fix(autofix): charge regressions to the brake and gate test weakening - #10188
fix(autofix): charge regressions to the brake and gate test weakening#10188wenshao wants to merge 32 commits into
Conversation
Two holes let an autofix round ship a new problem for free. A round that pushed a fix and turned CI red counted as a SUCCESS: the consecutive-failure brake counts "rounds that pushed nothing", so the red it created reset the counter, came back as the next round's input, and was paid for out of the round budget. A PR could alternate regress/repair indefinitely while every brake read it as converging. Every acted round now stamps what it pushed and whether the head it pushed onto was fully green; the next round charges a regression only when the live head is exactly that sha, the marker says green, the window key matches, and the head is red now. The observing round records it in whichever report it posts, and the brake stops resetting on a regressing round. Attribution is conservative on every axis: a moved head, a re-arm, a pending check at push time, a cancelled run, or the loop's own lanes all drop the charge. The second hole: relaxing an existing assertion was invisible to the whole gate. Build/typecheck/lint never read assertions, the package tests run the weakened file, and the bite check reads only the tests a round ADDS. The SKILL already required content evidence for deleting or weakening a test; nothing enforced it. The gate now measures deleted test files, net assertion removal, and added skip/todo markers, and rejects the round unless each file is recorded in test-weakening.json with a reason. It judges that the claim exists, not that it is right — the reasons ride into the round report for a maintainer to read against the diff. Deletions get their own merge-freight discriminator: content equality reads "absent on both sides" as identical, so a round deleting a test the PR itself added looked like freight and escaped. The merge base separates the two cases.
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
Thanks for the PR, @wenshao. The two holes are real, I checked both against the current tree: the brake headline literally reads "consecutive rounds that pushed nothing", the gate's own comments defer full regression to post-push CI ("Full regression is covered by regular CI on the PR after the push"), and nothing in the loop accounts for a regression at all; on the test side, the bite check runs the round's own test files against the pre-round tree, so a file with assertions edited away passes trivially, and the SKILL's "deleting or weakening tests requires content evidence" rule is prose with no enforcement. The direction — deferred, conservatively-attributed regression accounting plus a claim-exists gate for weakening — is sound.
One gate item before we go deeper, though: the PR body doesn't follow the PR template. None of the required sections are present:
## What this PR does## Why it's needed## Reviewer Test Plan— including### How to verify,### Evidence (Before & After)(N/A is fine here — nothing user-visible), and the### Tested onOS matrix## Risk & Scope## Linked Issues(none linked — say so explicitly)
All the substance is already in the body — sections 1 and 2 cover What/Why, ## Verification maps to the Reviewer Test Plan, and ## Deliberately not in this change fits under Risk & Scope — so this is a restructure rather than new work. Once the body follows the template, re-run with @qwen-code /triage and we'll pick up from the code review.
中文说明
感谢这个 PR,@wenshao。这两个口子都是真实存在的,我在当前代码树上逐一核对过:熔断的标题文案就是「consecutive rounds that pushed nothing」,门自己的注释也写明把全量回归推迟到 push 之后交给 PR 的 CI("Full regression is covered by regular CI on the PR after the push"),整个循环里没有任何「回归」的记账概念;测试这一侧,bite 检查是拿本轮自己的测试文件去跑改动前的树,因此一个断言被改掉的文件可以平凡地通过,而 SKILL 里「删除或弱化测试需要内容证据」的规则只是散文,没有任何强制。方向——延迟记账、归因保守的回归统计,加上「声明是否存在」的弱化门——是合理的。
但在深入之前有一个门禁项:PR 正文没有遵循 PR 模板。必填章节全部缺失:
## What this PR does## Why it's needed## Reviewer Test Plan—— 包括### How to verify、### Evidence (Before & After)(本 PR 无用户可见行为,写 N/A 即可)和### Tested on操作系统矩阵## Risk & Scope## Linked Issues(没有关联 issue 也请显式说明)
正文里其实已经包含了全部实质内容——第 1、2 节对应 What/Why,## Verification 对应 Reviewer Test Plan,## Deliberately not in this change 适合放进 Risk & Scope——所以这是一次重组,而不是新增工作。正文按模板调整后,用 @qwen-code /triage 重新触发,我们会从代码审查继续。
— Qwen Code · qwen3.8-max
Code Coverage Summary
CLI Package - Full Text ReportCore Package - Full Text ReportFor detailed HTML reports, please see the 'coverage-reports-22.x-ubuntu-latest' artifact from the main CI run. |
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
Partially reviewed — gaps disclosed.
Not reviewed: reverse audit — stopped before round 9 by the review time budget.
Not linted (tool limitation, not a blocker): the executable-script lint — .github/workflows/qwen-autofix.yml: actionlint embedded-shell source mapping is not yet supported — not linted.
中文说明
仅完成部分审查,审查缺口已披露。
未审查:反向审计——评审时间预算不足,未能开始第 9 轮。
未检查(工具限制,非阻断):the executable-script lint — .github/workflows/qwen-autofix.yml: actionlint embedded-shell source mapping is not yet supported — not linted。
— qwen3.8-max via Qwen Code /review (v0.22.2)
|
@qwen-code /takeover |
|
🤝 Takeover engaged: the autofix loop now manages this PR — it will address new review feedback and resolve base conflicts until the label is removed or the round cap is reached. Remove the 中文说明🤝 已接管:autofix 循环现在管理此 PR —— 将持续处理新的评审反馈与 base 冲突,直到移除标签或达到轮次上限。移除 |
…d-test-weakening-gates # Conflicts: # .github/workflows/.size-baseline # .github/workflows/qwen-autofix.md
|
@qwen-code /resolve |
|
🤖 Addressed the latest review feedback (round 1/100). What changed, and what I pushed back on: · 已处理最新评审反馈(第 1/100 轮)。改动内容与我反驳保留之处如下: PR #10188 — round 2: conflict resolved, six Critical findings fixedBase conflict (
Findings addressed this round (10):
The redesign also keeps the original "pre-existing" doctrine — files absent from the pre-round ref are not measured ( Deferred to the next round (9) — batch bounded at the Criticals first: R1-4 (salvage-merge Review bodies: rv:5033144635 (triage) asks for the PR body to follow the PR template — that is a GitHub edit the workflow owns in this mode; the body already carries all substance, so this is a restructure the posting step or a maintainer can apply. rv:5035129687 is a partial-review disclosure (reverse audit not reached, actionlint embedded-shell not supported) — informational, no action. Verification
中文说明PR #10188 —— 第 2 轮:冲突已解决,六项 Critical 发现已修复基分支冲突(
本轮处理的发现(10 项):
重设计还保留了原有「既有测试」语义——改动前 ref 不存在的文件不被测量( 推迟到下一轮(9 项) —— 本轮批次以 Critical 为限:R1-4(抢救合并的 审查主体: rv:5033144635(triage)要求 PR 正文遵循 PR 模板——这是本模式下由工作流拥有的 GitHub 编辑;正文已包含全部实质内容,属重组性质,可由发布步骤或维护者执行。rv:5035129687 为部分审查披露(反向审计未及、actionlint 内嵌 shell 不支持)——信息性,无需行动。 验证
Base-conflict check · 基分支冲突检查: conflicted with main — resolved in this push. · 与 main 有冲突——已在本次推送中解决。 Re-review when you have a moment. After round 100 this bot stops and leaves the PR for a human. · 有空请复审;第 100 轮后本 bot 停止并将 PR 交给人工。 🧠 Handled by Qwen Code · model/模型 |
|
Qwen Code did not run conflict resolution for this request. PR #10188 does not currently have merge conflicts with main. |
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
Partially reviewed — gaps disclosed.
Not reviewed: build-and-test — Test (macos-latest, Node 22.x) and Test (windows-latest, Node 22.x) were skipped in CI at this commit (they do not run on pull_request events); the suite was run locally on Linux/bash 5.2 — the lanes' native bash behavior could not be exercised here.
Not reviewed: reverse audit — stopped before round 5 by the review time budget.
Not linted (tool limitation, not a blocker): the executable-script lint — .github/workflows/qwen-autofix.yml: actionlint embedded-shell source mapping is not yet supported — not linted.
Deferred under the convergence posture (round 2, not a blocker) — recorded, not requested in this round:
scripts/tests/qwen-autofix-workflow.test.js:16663 — [review] windowed regression charging pinned only on the negative case — select(.[1] == $win) mutation survives the suitescripts/tests/qwen-autofix-workflow.test.js:16685 — [review] ZH headline cause clause '或推送后检查转红' unpinned — deletion mutation keeps 246/246 greenscripts/tests/qwen-autofix-workflow.test.js:22088 — [review] classifier red-list members beyond FAILURE unpinned — TIMED_OUT-drop mutation survives and classifies greenscripts/tests/qwen-autofix-workflow.test.js:16658 — [review] whole-round matching pin oriented backwards — spaceless-matcher mutant survives, marker-substring direction untested.github/workflows/qwen-autofix.yml:5892 — [review] regression-marker author filter has zero test witness — forged human marker charges a push under the drop mutationscripts/tests/qwen-autofix-workflow.test.js:22187 — [review] LAST_PUSH filter-before-newest ordering unpinned — newest-then-filter mutant shadows the bot marker, suite blindscripts/tests/qwen-autofix-workflow.test.js:22234 — [review] env-wiring pins count-only, placement unpinned — relocation mutant keeps count 2 and 246/246 green.qwen/skills/autofix/SKILL.md:492 — [review] SKILL marker enumeration omits .fails — an agent following it literally loses a round to the gate (narrowed: .fails only)
中文说明
仅完成部分审查,审查缺口已披露。
未审查:build-and-test — Test (macos-latest, Node 22.x) and Test (windows-latest, Node 22.x) were skipped in CI at this commit (they do not run on pull_request events); the suite was run locally on Linux/bash 5.2 — the lanes' native bash behavior could not be exercised here。
未审查:反向审计——评审时间预算不足,未能开始第 5 轮。
未检查(工具限制,非阻断):the executable-script lint — .github/workflows/qwen-autofix.yml: actionlint embedded-shell source mapping is not yet supported — not linted。
收敛姿态下延后(第 2 轮,非阻断)——已记录,本轮不要求修改:共 8 条(原文未翻译,列表见上方英文部分)。
— qwen3.8-max via Qwen Code /review (v0.22.2)
|
🤖 Addressed the latest review feedback (round 2/100). What changed, and what I pushed back on: · 已处理最新评审反馈(第 2/100 轮)。改动内容与我反驳保留之处如下: Autofix round summary — PR #10188 (review-address, critical-only growth-audit round)Growth audit (required before any edit this round): Mode: Critical-only (the growth brake engaged on the test budget, src 75 / test 421 vs 400/400). All Suggestion-level findings (R2-8…R2-22, R1-4…R1-18) remain deferred exactly as the brake dictated — no code, threads, or replies for them this round. No base merge ( Commit: Feedback dispositions (8 Criticals, all resolved in code)
The three classifier findings conflicted on the literal shape of the fix (R2-5 kept the carve-out, R2-6 made it COMPLETED-only, R2-7 dropped it); the implemented union satisfies all three proved defects — five-name exclusion, no carve-out in the charge verdict — and preserves the conclusion-set agreement (red list, CANCELLED-as-green) the scan's Mutation probes (each guard witnessed; all reverted)
Verification
中文说明Autofix 轮次摘要 — PR #10188(review-address,仅 Critical 的增长审计轮)增长审计(本轮任何编辑之前必须完成): 模式: 仅 Critical(增长刹车在测试预算上触发:源码 75 / 测试 421,预算 400/400)。所有 Suggestion 级发现(R2-8…R2-22、R1-4…R1-18)完全按刹车指令保持延后 —— 本轮不为它们改代码、动线程或写回复。无 base 合并( 提交: 反馈处置(8 条 Critical,全部在代码中解决)
三条分类器发现在修复的字面形态上互相冲突(R2-5 保留豁免、R2-6 要求仅 COMPLETED、R2-7 要求移除);实现的并集同时关闭三个已被证明的缺陷 —— 五名字排除、记账判定中无豁免 —— 并保留与扫描端 变异探针(每个守卫都有见证;全部已还原)
验证
Deferred non-Critical feedbackCritical-only mode is active: the PR's diff grew src 75 / test 421 net lines beyond this counting window's baseline (budgets: 400/400). The workflow excluded the non-Critical feedback below from this round's actionable sections; the items remain open for human follow-up. Maintainer feedback continues to flow unaffected during a growth-only engagement (the per-author batch budget applies only after 5 change-producing rounds). ( 中文说明已进入仅处理 Critical 的模式:本计数窗口内 diff 净增长已达 源码 75 / 测试 421 行(预算 400/400)。本轮可执行区域已排除下方非 Critical 反馈;这些条目保持开放,留待人工跟进。纯增长触发期间维护者反馈照常流动(按作者的批次预算仅在完成 5 个产生改动的轮次后生效)。(评论 Base-conflict check · 基分支冲突检查: no conflict with main. · 与 main 无冲突。 Re-review when you have a moment. After round 100 this bot stops and leaves the PR for a human. · 有空请复审;第 100 轮后本 bot 停止并将 PR 交给人工。 🧠 Handled by Qwen Code · model/模型 |
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
Partially reviewed — gaps disclosed.
3 Suggestion-level finding(s) this review confirmed are already reported on this PR and are not repeated:
- Whole-round matching pin oriented backwards (marker-substring direction untested) — already recorded in round 2's deferral list (review 5038649207, scripts/tests/qwen-autofix-workflow.test.js:16658)
- ZH headline cause clause unpinned (或推送后检查转红) — already recorded in round 2's deferral list (review 5038649207, scripts/tests/qwen-autofix-workflow.test.js:16685)
- Classifier red-list members beyond FAILURE unpinned (incl. untested lane-name exclusions) — already recorded in round 2's deferral list (review 5038649207, scripts/tests/qwen-autofix-workflow.test.js:22088)
Not reviewed: build-and-test — Test (macos-latest, Node 22.x) and Test (windows-latest, Node 22.x) were skipped in CI at this commit (they do not run on pull_request events); the suite ran locally on Linux/bash 5.2 — the bash-3.2 lane's native behavior was verified by inspection only (no bash-4 construct before the mapfile boundary; the new boundary pin charges declare -A only — see the pin-narrowness finding).
Not explored to full depth (tool budget reached): chunk 6: executing scripts/tests/qwen-autofix-workflow.test.js to observe the two chunk-6 tests green — vitest is unavailable (no node_modules in this review worktre…; "agent 1c": none — though I did not execute the new vitest suites myself (static trace only); lane analysis is from the vitest config and ci.yml, not an observed run..
Not linted (tool limitation, not a blocker): the executable-script lint — .github/workflows/qwen-autofix.yml: actionlint embedded-shell source mapping is not yet supported — not linted.
Convergence: round 3 posted 37 inline comment(s), 12 of them reported for the first time; the previous round posted 32 (23 new). Findings keep coming back to the same files: .github/scripts/run-autofix-review-verification.sh (findings in rounds 1, 2; 6 more now); scripts/tests/qwen-autofix-workflow.test.js (findings in rounds 1, 2; 3 more now); .github/scripts/autofix-push-and-report.sh (findings in round 1; 1 more now). A cluster that keeps producing siblings usually means the fixes are treating instances of a shared root cause — triaging that cause before the next round, or splitting an independent cluster into its own pull request, tends to end the loop faster than fixing them one at a time. (Observation only — nothing was withheld from this review because of this observation.)
中文说明
仅完成部分审查,审查缺口已披露。
本轮确认的 3 条建议级发现已在 PR 上报告过,不再重复发布(列表见上方英文部分)。
未审查:build-and-test — Test (macos-latest, Node 22.x) and Test (windows-latest, Node 22.x) were skipped in CI at this commit (they do not run on pull_request events); the suite ran locally on Linux/bash 5.2 — the bash-3.2 lane's native behavior was verified by inspection only (no bash-4 construct before the mapfile boundary; the new boundary pin charges declare -A only — see the pin-narrowness finding)。
未探索到全部深度(达到工具调用预算):chunk 6:executing scripts/tests/qwen-autofix-workflow.test.js to observe the two chunk-6 tests green — vitest is unavailable (no node_modules in this review worktre…;"agent 1c":none — though I did not execute the new vitest suites myself (static trace only); lane analysis is from the vitest config and ci.yml, not an observed run.。
未检查(工具限制,非阻断):the executable-script lint — .github/workflows/qwen-autofix.yml: actionlint embedded-shell source mapping is not yet supported — not linted。
收敛情况:第 3 轮发布了 37 条行内评论,其中 12 条是首次提出;上一轮发布了 32 条(其中 23 条首次提出)。发现反复回到同一批文件:.github/scripts/run-autofix-review-verification.sh(第 1、2 轮已出过发现,本轮又有 6 条);scripts/tests/qwen-autofix-workflow.test.js(第 1、2 轮已出过发现,本轮又有 3 条);.github/scripts/autofix-push-and-report.sh(第 1 轮已出过发现,本轮又有 1 条)。一个不断再生兄弟发现的簇,通常意味着逐条修复只在处理同一根因的实例——先定位并处理该根因,或把独立的簇拆成单独的 PR,通常比逐条修复更快结束循环。(仅为观察——本轮评审未因此扣留任何内容。)
— qwen3.8-max via Qwen Code /review (v0.22.2)
|
@qwen-code /resolve |
|
Qwen Code attempted to resolve merge conflicts but the run did not complete successfully. Check the workflow run for full logs. |
…d-test-weakening-gates # Conflicts: # .github/workflows/.size-baseline # .github/workflows/qwen-autofix.md
|
🤖 Addressed the latest review feedback (round 1/100). What changed, and what I pushed back on: · 已处理最新评审反馈(第 1/100 轮)。改动内容与我反驳保留之处如下: Round summary — PR #10188 (autofix regression + test-weakening gates)This round did three things: merged Base conflict (
|
| Mutation | Expected red test | Result |
|---|---|---|
Drop --is-ancestor provenance guard |
side-branch merge charge | red ✓ |
Revert stripper to //-only sed |
single-line block comment | red ✓ |
| Remove blob-density probe | multi-line block wrap | red ✓ (single-line stays green — witnesses separated) |
| Restore del-side stripping | in-string // removal |
red ✓ |
Drop --text |
planted -diff attribute |
red ✓ |
| Re-introduce newline record split | newline fragment ack | red ✓ |
| Re-introduce tab record split | tab prefix-fragment ack | red ✓ |
All mutations reverted; full file green again after restore.
Verification
Commands actually run this round (post-merge tree, then post-fix tree):
git merge origin/main— 2 conflicts resolved, merge committed (7417b92)npm run build— passednpm run typecheck— passednpm run lint— passednpx vitest run --config ./scripts/tests/vitest.config.ts scripts/tests/qwen-autofix-workflow.test.js— 265/265 passed (256 pre-existing + 9 new)npm run test:scripts(full scripts lane) — 1852 passed / 1 failed; the single failure (install-script.test.js, standalone packaging) reproduced as a missingpackages/audio-capture/distbuild artifact, passed afternpm run build(105 passed) — environment state, not a code defectbash .github/scripts/check-workflow-size.shwithWORKFLOW_SIZE_BASE_SHA=<merge base>— passed; baseline line re-bumped to the merged size 434790 in the same changebash -non the gate script — cleannpx prettier --checkon the test file — clean (after--write)- 7 mutation probes, each red on the target test, then full green after restore (table above)
- macOS/Windows lanes do not run on pull_request events; the bash-3.2-sensitive paths stay gated by
hasBashMapfileexactly as the existing suite requires
中文说明
本轮摘要 — PR #10188(autofix 回归记账 + 测试弱化门)
本轮做了三件事:合并 origin/main(两处冲突,见下文)、关闭第 3 轮审查在测试弱化门中发现的六个新 Critical 逃逸类,并逐一核实本 PR 上所有较早的 Critical 发现已在当前代码树中修复(均对照合并后的代码复查,相应线程列入已解决清单)。
base 冲突(--conflict true)
合并 origin/main(44 个提交),两处冲突:
.github/workflows/.size-baseline—— 本分支记录 410372、main 记录 426924(qwen-autofix.yml)。按合并后文件的实际大小(434790)解决;这同时关闭了 R3-5 基线过期发现:棘轮行现在与它所跟踪的文件逐字节一致。.github/workflows/qwen-autofix.md—— 理由目录编号冲突:main 落入了 af-149/af-150(轮心跳、深链接),本分支也有自己的 af-149(回归分类器)。保留 main 的编号不动(已上线且被心跳代码引用),把本 PR 的条目改号为 af-151,同步更新工作流、push-and-report 脚本与测试文件中共 10 处引用。R3-11 要求的措辞修正随本次改号一并完成:af-151 块不再声称分类器「以反馈渲染器排除自身检查的同样方式」排除——现按实现表述(规范的五名单整体排除,刻意不采用反馈渲染器的 review-address 豁免)。
本轮处理的 Critical 发现(全部先在改动前代码上复现,全部带变异见证 fixture)
- 侧支合并反转 freight 归属(R1-13 仍然成立):freight 过滤器把任何合并的第二父提交当作「main 一侧」;一轮在侧支上弱化再合并回来即可把所有信号归零。修复:仅当第二父提交源自 main(
merge-base --is-ancestor)时才应用 freight 归属,否则第一父 diff 完全属于本轮。Fixtureside-branch-merge。 - 块注释逃逸(R2-4 修复引入):两种机制——单行
/* expect(...) */副本抵消删除;多行包裹时断言行逐字节不变,-U0把它锚定为隐式上下文、根本不出现在 diff 中(探针实测:只出现裸/*/*/两行)。修复:剥离器带跨行块状态处理两种 TS 注释形态、只作用于新增侧;当某文件的编辑不触动任何行级信号时,改用「去注释 blob 断言密度」比较记账。Fixtureblock-comment-line、block-comment-wrap(两个见证彼此分离:去掉剥离器扩展只使前者变红,去掉 blob 探针只使后者变红)。 - 字符串内
//归零被删断言(新 Critical):剥离器的s|//.*$||在删除侧也会于 URL 字符串内的//处截断,删除... 'https://...'; expect(...)行永不被计数。修复:删除行按原始计数、新增行按剥离后计数(镜像的误记账方向是失败保守方向,一条承认证录即可回答)。Fixtureurl-line。 .gitattributes -diff归零测量(R3-4):分支植入的-diff/binary规则把测量 diff 塌缩成二进制横幅——文件仍被枚举而四个计数器全零。修复:测量生产者加--text。Fixtureattr-binary(fixture 中与生产一致的授权方式:PR 足迹本身触碰.gitattributes)。- 文件名含换行的碎片承认(R3-2):行尾 LF 把换行拼接的
WEAKENED记录拆碎;为碎片写承认即可通过门,诚实的完整名承认反而被拒。 - 文件名含制表符的碎片承认(R3-3):制表符在承认循环的
IFS=$'\t' read处拆分记录;前缀碎片承认即可通过。
两者统一修复:测量记录全程以平行索引数组承载,承认按 base64 编码的原始路径字节精确匹配(分支可控字节从不经过 shell 解析解码),advisory 渲染使用同一编码集(@base64d)。Fixturenewline-name、tab-name,各含碎片承认拒绝测试与诚实承认接受测试。
较早 Critical 发现 —— 已在当前树中核实修复(线程列入已解决)
均对照合并后代码逐条复查(含 fixture 见证):R1-1 skip 标记形态(fails、skip-each)、R1-2 expect.poll(poll)、R1-3 五个绿路径 it.skipIf(!hasBashMapfile) 守卫、R1-13 轮提交归属(merge-escape)、R1-14 :(literal) pathspec(literal)、R1-15 还原 main 字节(revert-main)、R1-13 角落 (a)/(b)(merge-conflict-drop、merge-modify-delete)、R2-1 bash-3.2 边界(索引数组 + 边界测试)、R2-2 删除/重建(delete-readd)、R2-4 符号链接 typechange(symlink)、R2-3 // 注释化(comment-out)、R2-5/R2-6/R2-7 自身 lane 分类器(五名单整体排除;红 review-pr、进行中自身检查、失败自身轮 + 有效标记三种 fixture)。另有 R1-8 freight fixture、R1-16 标记相抵(skiptouch)、R1-17 恢复的 workdirFiles 文档注释、R1-19 grep -- 守卫(dash-ack)。
延期到下一轮的建议(轮容量所限,非拒绝)
本轮约 8 条的容量被六个 Critical + 基线更新 + af-151 措辞修正占满。已在各线程回复延期:R1-4(推送竞态合并下的 pre=)、R1-5(渲染器 ≥40 字符门槛)、R1-6(死 CHECK_STATE env)、R1-7(共享消毒助手)、R1-9(spec/快照 pathspec fixture)、R1-10(畸形承认文件 fixture)、R1-11(push 标记分支/顺序钉)、R1-12(round N/M 交叉钉)、R1-18(EXPECTED/STALE/STARTUP_FAILURE)、R2-8(expect.soft)、R2-9(渲染下界钉)、R2-10(括号访问)、R2-11(枚举退出状态)、R2-12(原始路径可承认清单)、R2-13(连字符 UNAVAILABLE 钉)、R2-14(中和形态钉)、R2-15(标记前链式修饰符)、R2-16(翻动 advisory)、R2-17(details/summary/上限见证)、R2-18(中文标题钉)、R2-20(each 表行)、R2-21(常量 skipIf/runIf)、R2-22(未演练 RE 臂)、R3-6(失败开放 fixture)、R3-7(边界拒绝清单宽度)、R3-8(未知 fixture 抛错)、R3-9(双文件 fixture)、R3-10(no-op 🩸 披露)。R2-19 作为已文档化的设计局限记录在案——未要求也未做任何代码改动。
审查主体
- 1a 阶段 triage(模板合规):本轮同时按仓库 PR 模板写出
pr-title.txt/pr-body.md,供工作流更新 PR 正文;实质内容已在正文中,这只是重组。 - 三份「部分审查——缺口披露」主体为披露记录,无需行动。
变异探针(提交前逐一验证每个守卫的见证)
| 变异 | 应变红的测试 | 结果 |
|---|---|---|
去掉 --is-ancestor 来源守卫 |
侧支合并记账 | 红 ✓ |
剥离器还原为仅 // 的 sed |
单行块注释 | 红 ✓ |
| 移除 blob 密度探针 | 多行块包裹 | 红 ✓(单行保持绿——见证彼此分离) |
| 恢复删除侧剥离 | 字符串内 // 删除 |
红 ✓ |
去掉 --text |
植入 -diff 属性 |
红 ✓ |
| 重新引入换行记录拆分 | 换行碎片承认 | 红 ✓ |
| 重新引入制表符记录拆分 | 制表符前缀碎片承认 | 红 ✓ |
全部变异已还原;还原后整套重新为绿。
验证
本轮实际执行的命令(合并后树 + 修复后树):
git merge origin/main—— 解决 2 处冲突,合并已提交(7417b92f72)npm run build—— 通过npm run typecheck—— 通过npm run lint—— 通过npx vitest run --config ./scripts/tests/vitest.config.ts scripts/tests/qwen-autofix-workflow.test.js—— 265/265 通过(256 既有 + 9 新增)npm run test:scripts(scripts 全 lane)—— 1852 通过 / 1 失败;唯一失败(install-script.test.js,独立打包)复现原因为缺少packages/audio-capture/dist构建产物,npm run build后通过(105 通过)——环境状态问题,非代码缺陷bash .github/scripts/check-workflow-size.sh(WORKFLOW_SIZE_BASE_SHA=<合并基>)—— 通过;基线行已在同一变更中按合并后大小 434790 重新更新- 门脚本
bash -n—— 干净 - 测试文件
npx prettier --check—— 干净(--write之后) - 7 个变异探针,各自使目标测试变红,还原后全套重新为绿(见上表)
- macOS/Windows lane 在 pull_request 事件下不运行;bash-3.2 敏感路径继续按既有套件要求由
hasBashMapfile守卫
🧭 Gate advisory — this round modified areas outside the PR footprint (machine-measured, not agent-authored):
- packages/core
Review the expansion deliberately; the footprint gate is in advisory mode. · 本轮改动了 PR 足迹之外的区域(门自动测量,非 agent 文本),当前足迹门为 advisory 模式,请有意识地审阅该扩张。
Base-conflict check · 基分支冲突检查: conflicted with main — resolved in this push. · 与 main 有冲突——已在本次推送中解决。
Re-review when you have a moment. After round 100 this bot stops and leaves the PR for a human. · 有空请复审;第 100 轮后本 bot 停止并将 PR 交给人工。
🧠 Handled by Qwen Code · model/模型 qwen3.8-max
|
Qwen Code review did not complete successfully. The review pipeline failed before a review could be posted. A transient error is retried automatically; if you are seeing this, retry with |
|
Qwen Code review did not complete successfully. The review pipeline failed before a review could be posted. A transient error is retried automatically; if you are seeing this, retry with |
|
🔀 Base updated: red check(s) [review-pr] pass on current main — merged current main via update-branch; CI will re-run. 中文说明🔀 已更新 base:红色检查 [review-pr] 在当前 main 上通过 —— 已通过 update-branch 合入当前 main,CI 将重新运行。 |
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
Partially reviewed — gaps disclosed.
8 Suggestion-level finding(s) this review confirmed are already reported on this PR and are not repeated:
- salvage-merged push pre= stamp (autofix-push-and-report.sh:651) — already reported as R1-4 (comment 3866778394)
- whole-round matching pin mirror direction (qwen-autofix-workflow.test.js:18021) — already recorded in the round-2 deferral list (review 5038649207, test:16658)
- marker-emission static source-text pin (qwen-autofix-workflow.test.js:27735) — already reported as R1-11 (comment 3869934695)
- enumeration producers discard exit status (run-autofix-review-verification.sh:1842) — already reported as R2-11 (comment 3869934606)
- .size-baseline understates landed size by 319 bytes (.github/workflows/.size-baseline:38) — already reported (comment 3872532141)
- literal-boolean skipIf(true)/runIf(false) escape (run-autofix-review-verification.sh:1127) — already reported as R2-21 (comment 3869934657)
- EXPECTED/STARTUP_FAILURE classify green (qwen-autofix.yml:4744) — already reported as R1-18 (comment 3869934706)
- ZH headline cause clause unpinned (qwen-autofix-workflow.test.js:18047) — already recorded in the round-2 deferral list (review 5038649207, test:16685)
Not reviewed: build-and-test — Test (macos-latest, Node 22.x) and Test (windows-latest, Node 22.x) were skipped in CI at this commit (they do not run on pull_request events); the changed suites ran locally on Linux/bash 5.2 (538 passed) — the lanes' native bash behavior could not be exercised here.
Not reviewed: the executable-script lint — .github/scripts/autofix-push-and-report.sh: shellcheck is not installed.
Not reviewed: the executable-script lint — .github/scripts/run-autofix-review-verification.sh: shellcheck is not installed.
Not explored to full depth (tool budget reached): "agent reverse-audit (round 5)": line-by-line walk of the ~5,600-line gate-test fixture hunk (diff lines 1422–7066) — walked instead by test-name census, runGate harness body, skipIf/host-probe….
Not linted (tool limitation, not a blocker): the executable-script lint — .github/workflows/qwen-autofix.yml: actionlint embedded-shell source mapping is not yet supported — not linted.
Deferred under the convergence posture (round 18, not a blocker) — recorded, not requested in this round:
scripts/tests/qwen-autofix-workflow.test.js:25701 — [probe] host-probe pin omits the two escapeAwk freight tests — a dropped gate on them stays green on every PR lanescripts/tests/qwen-autofix-workflow.test.js:27113 — [probe] untested gate arms: hasAssertions member and condition-valued options exemption — mutants survive all 147 gate tests.github/scripts/run-autofix-review-verification.sh:1150 — [probe] assertion RE whitelist blind to expectTypeOf( — deleting type-level assertions measures zero
Convergence: round 18 posted 10 inline comment(s), 2 of them reported for the first time; the previous round posted 8 (0 new). Findings keep coming back to the same files: .github/scripts/run-autofix-review-verification.sh (findings in rounds 8, 10, 14, 15; 2 more now). A cluster that keeps producing siblings usually means the fixes are treating instances of a shared root cause — triaging that cause before the next round, or splitting an independent cluster into its own pull request, tends to end the loop faster than fixing them one at a time. (Observation only — nothing was withheld from this review because of this observation.)
Mechanism health: this round did not close cleanly, so it withholds the incremental anchor — and the round it recovered had no anchor this round could use either — none at all, one with no certifier, one certified by an identity other than the one this round runs under, or one this round's fetch refused or resolved to the head — so the next review re-reads the whole diff unless recovery grafts an earlier own anchor that the round running it can use onto the complete work list this round leaves behind, and keeps doing so until a round's marker carries an anchor again or a graft lands that the round running it can use. (Stated, not acted on — this changes nothing about what the round posts.)
Residual risk: this loop is persistently critical — Criticals stood in the previous round's work-list and stand again this round (10 Critical(s)), the rate of first-time findings is not falling (this round 2, previous 0), and the standing Critical backlog is not shrinking. The severity floor will not converge it. Recommendation: land-with-residual-risk — the exit is a maintainer risk-acceptance decision (merge, carrying the residual risk), not another review round. Residual-risk inventory for that decision (maintainer to complete):
| standing Critical | attack surface | attacker-dependency | blast radius |
|---|---|---|---|
| (each standing Critical) | … | … | … |
Advisory only — it does not block this review.
中文说明
仅完成部分审查,审查缺口已披露。
本轮确认的 8 条建议级发现已在 PR 上报告过,不再重复发布(列表见上方英文部分)。
未审查:build-and-test — Test (macos-latest, Node 22.x) and Test (windows-latest, Node 22.x) were skipped in CI at this commit (they do not run on pull_request events); the changed suites ran locally on Linux/bash 5.2 (538 passed) — the lanes' native bash behavior could not be exercised here。
未审查:the executable-script lint — .github/scripts/autofix-push-and-report.sh: shellcheck is not installed。
未审查:the executable-script lint — .github/scripts/run-autofix-review-verification.sh: shellcheck is not installed。
未探索到全部深度(达到工具调用预算):"agent reverse-audit (round 5)":line-by-line walk of the ~5,600-line gate-test fixture hunk (diff lines 1422–7066) — walked instead by test-name census, runGate harness body, skipIf/host-probe…。
未检查(工具限制,非阻断):the executable-script lint — .github/workflows/qwen-autofix.yml: actionlint embedded-shell source mapping is not yet supported — not linted。
收敛姿态下延后(第 18 轮,非阻断)——已记录,本轮不要求修改:共 3 条(原文未翻译,列表见上方英文部分)。
收敛情况:第 18 轮发布了 10 条行内评论,其中 2 条是首次提出;上一轮发布了 8 条(其中 0 条首次提出)。发现反复回到同一批文件:.github/scripts/run-autofix-review-verification.sh(第 8、10、14、15 轮已出过发现,本轮又有 2 条)。一个不断再生兄弟发现的簇,通常意味着逐条修复只在处理同一根因的实例——先定位并处理该根因,或把独立的簇拆成单独的 PR,通常比逐条修复更快结束循环。(仅为观察——本轮评审未因此扣留任何内容。)
机制健康:本轮未能干净收尾,因而扣留了增量锚点,而它恢复到的那一轮也没有留下本轮可用的锚点——要么完全没有、要么没有认证者、要么由本轮运行身份之外的身份认证、要么被本轮的获取拒绝或解析为头提交——因此下一次评审将重读整个 diff,除非恢复流程把本轮能使用的更早自有锚点嫁接到本轮留下的完整工作清单上;并会一直如此,直到某一轮的标记重新带上锚点,或落地的嫁接能被运行该轮的评审使用。(仅陈述,不据此行动——这不改变本轮发布的任何内容。)
残余风险:本循环处于 persistently-critical 形态——上一轮工作清单中的 Critical 本轮依然存在(本轮 10 条 Critical),首次发现的速率没有下降(本轮 2,上一轮 0),且未决 Critical 积压没有减少。severity floor 无法使其收敛。建议:land-with-residual-risk——出口是 maintainer 的风险接受决定(合入并承担残余风险),而非再开一轮评审。供该决定使用的残余风险清单(maintainer 填写):按每条未决 Critical 列出「攻击面 · 攻击者依赖性 · 影响范围」三栏。仅为建议——不阻断本次评审。
— qwen3.8-max via Qwen Code /review (v0.22.3)
…ard, and JSX escapes (#10188)
|
🤖 Addressed the latest review feedback (round 13/100). What changed, and what I pushed back on: · 已处理最新评审反馈(第 13/100 轮)。改动内容与我反驳保留之处如下: Round summary — PR #10188 (Critical-only window)This round implemented the minimal blocking subset of the standing Critical Feedback points and decisionsImplemented (resolved in code this round):
Deferred to the next round (replies posted on each thread):
Not work this round: the VerificationReproduction first: all six new escape fixtures were added and confirmed RED
中文说明轮次摘要 — PR #10188(仅处理 Critical 的窗口)按照预算警告(上一轮已耗尽 agent 时间预算)与轮次上限(约 8 条发现),本轮实现了未决 反馈要点与决定本轮已在代码中实现(已解决):
延后到下一轮(已在各线程回复):
本轮不处理: 验证先复现:六个新逃逸用例先行加入,并确认在修复前的门上为红(含翻转的 skiptouch 钉测在内共
Deferred non-Critical feedbackCritical-only mode is active: the round counter reached 5 (this window was seeded at round 10 by
中文说明已进入仅处理 Critical 的模式:轮次计数已达 5(本窗口由 Base-conflict check · 基分支冲突检查: no conflict with main. · 与 main 无冲突。 🧵 Resolved all 22 selected review thread(s). · 已关闭全部选中的 22 条评审线程。 Re-review when you have a moment. After round 100 this bot stops and leaves the PR for a human. · 有空请复审;第 100 轮后本 bot 停止并将 PR 交给人工。 🧠 Handled by Qwen Code · model/模型 |
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
Partially reviewed — gaps disclosed.
14 Suggestion-level finding(s) this review confirmed are already reported on this PR and are not repeated:
- in-place matcher relaxation measures zero — already reported as R2-16 (comment 3869934632)
- enumeration producers discard their exit status — already reported as R2-11 (comment 3869934606)
- no-op and failure arms omit the visible regression disclosure — already reported (comment 3872532217)
- CANCELLED/EXPECTED/NEUTRAL/OFF classify as green — already reported as R1-18 (comment 3869934706)
- salvage-merged push stamps pre=green — already reported as R1-4 (comment 3866778394)
- .size-baseline records a stale byte count — already reported (comment 3872532141)
- host-probe pin list omits newly added mapfile-crossing flows — already reported (comment 3878061616)
- pushed-headline round shape unpinned against the emitter — already reported as R1-12 (comment 3869934701)
- position-free toContain pins on the push-marker capture — already reported as R1-11 (comment 3869934695)
- CHECK_STATE unread in the second report step — already reported as R1-6 (comment 3869934671)
- lossy safe-charset render versus exact base64 ack matching — already reported as R2-12 (comment 3869934612)
- no fixture exercises the WEAKEN_PATHSPEC branches — already reported as R1-9 (comment 3869934682)
- whole-number padding of the regressed-round match unpinned — already recorded in the round-2 deferral list (review 5038649207, test:16658)
- matched-WHOLE case oriented backwards, spaceless mutant survives — already recorded in the round-2 deferral list (review 5038649207, test:16658)
Not reviewed: build-and-test — Test (macos-latest, Node 22.x) and Test (windows-latest, Node 22.x) were skipped in CI at this commit (they do not run on pull_request events); the two changed suites ran locally on Linux/bash 5.2 (345 and 203 passed), so the lanes' native bash 3.2 behaviour could not be exercised.
Not reviewed: the executable-script lint — .github/scripts/autofix-push-and-report.sh: shellcheck is not installed.
Not reviewed: the executable-script lint — .github/scripts/run-autofix-review-verification.sh: shellcheck is not installed.
Not explored to full depth (tool budget reached): "Context: PR 10188 adds two deterministic gates to the…": executed merge-freight fixtures — the merge/ kept /fast-forward discriminators in brief item 4 were traced analytically only, no real merge repo was built.; "Context: PR 10188 adds two deterministic gates to the…": line-by-line read of the diff's ~5600-line test chunk (diff lines 1520–7143) — searched by grep and read at its boundaries, the JSX-decoy test, and the negative…; chunk 7: none — but note I could not execute the suite through vitest itself ( node_modules absent in this review worktree); every behavioural claim above comes from ru….
Not reviewed: reverse audit — stopped before round 3 by the review time budget.
Not linted (tool limitation, not a blocker): the executable-script lint — .github/workflows/qwen-autofix.yml: actionlint embedded-shell source mapping is not yet supported — not linted.
Deferred under the convergence posture (round 19, not a blocker) — recorded, not requested in this round; 2 Critical(s) among them are deferred by their axes — fails-closed on new surface, where no wrong result is certified and the merge base had neither the surface nor the defect — and remain follow-up work recorded in the findings artifact:
.github/scripts/run-autofix-review-verification.sh:1321 — [probe] Critical [fails-closed] [new-surface] weaken_strip_comments has no regex-literal arm and its inc flag is never reset per record, so an added /[/*]+/ swallows every later ….github/scripts/run-autofix-review-verification.sh:1571 — [probe] Critical [fails-closed] [new-surface] per-line grep freight filters are O(lines x blob bytes) inside a step capped at timeout-minutes: 60 — one real file in a nine-day-stale ….github/scripts/run-autofix-review-verification.sh:1616 — [probe] a pure reflow or re-indent of a multi-line assertion is charged as a removal, contradicting :1090 and SKILL.md:496 which both state an assertion moved within a file nets zero.github/scripts/run-autofix-review-verification.sh:1851 — [probe] a byte-identical git mv rename of a pre-existing test is published as net 2 assertion line(s) removed — the gate has no rename recognition (--no-renames at nine sites).github/scripts/run-autofix-review-verification.sh:1636 — [probe] four unbounded char-by-char whole-blob strip passes per measured file (six for merge-touched) with no size guard, on every round — a second cost against the same 60-minute ca….github/scripts/run-autofix-review-verification.sh:1703 — [probe] delete-then-re-add-weakened double-counts the same removals, so the machine-measured advisory reports net 3 assertion line(s) removed for a file that lost one.github/scripts/autofix-push-and-report.sh:630 — [probe] the published charge says the head's checks were all green while CHECK_STATE excludes five own-lane workflows first, so the checks page the sentence invites the maintainer to open s….github/workflows/qwen-autofix.yml:4754 — [probe] both ordering properties of the marker reader are unpinned — deleting sort_by(.at) survives the fixture that claims to pin it, because the fixture's array order and created_at order agreescripts/tests/qwen-autofix-workflow.test.js:27827 — [probe] no fixture puts a red lane beside an in-flight lane, so the classifier's red-before-pending precedence — which decides whether a charge fires on the most common live head state — i….github/scripts/autofix-push-and-report.sh:651 — [probe] a push that lands but whose report dies leaves no autofix-push marker and nothing backfills it, because the failure step's post block is gated on outcome != fixed — contradicting af-1….github/scripts/autofix-push-and-report.sh:651 — [probe] an in-round base merge (CONFLICT=true, which SKILL.md:513 instructs) stamps pre=green on a head carrying main's bytes, so main's own red is charged to the round — a distinct route fro….qwen/skills/autofix/SKILL.md:490 — [probe] the agent-facing enumeration names three disable shapes while WEAKEN_SKIP_RE charges six more families ( .fails , xit/xdescribe, computed accessor, options object, body skip(), suite), so a compli….github/workflows/qwen-autofix.yml:5849 — [review] test-weakening.json — the deciding agent-authored input of a rejecting gate — is the one round artifact missing from the Show run artifacts enumeration a test pins verbatim.qwen/skills/autofix/SKILL.md:572 — [probe] the SKILL side of the new filename contract has no pin although every sibling artifact does, so a SKILL-only drift rejects every weakening round with 345/345 tests greenscripts/tests/qwen-autofix-workflow.test.js:17986 — [probe] the fixture pinning four adjacent charged pushes drives a state the classifier cannot produce, so the motivating incident's real shape has no executing witness and its reachable ve…scripts/tests/qwen-autofix-workflow.test.js:24278 — [probe] a fixture comment states a false property of fixtureWrite (it single-quotes, not double-quotes) and uses it to justify the only three hand-rolled printf fixtures, whose bytes nothi…scripts/tests/qwen-autofix-workflow.test.js:17845 — [probe] the autofix-regression consumer's author filter has no witness at all — deleting it from qwen-autofix.yml:6416 leaves 345/345 green while foreign-authored rounds become chargeablescripts/tests/qwen-autofix-workflow.test.js:27897 — [probe] only two of the five own-lane workflow names are driven at the new classifier site and no textual pin of the list exists, so dropping any of the other three flips CHECK_STATE with …
Convergence: round 19 posted 9 inline comment(s), 4 of them reported for the first time; the previous round posted 10 (2 new). Findings keep coming back to the same files: .github/scripts/run-autofix-review-verification.sh (findings in rounds 8, 10, 14, 15, 18; 4 more now). The rate of new findings is not falling. A cluster that keeps producing siblings usually means the fixes are treating instances of a shared root cause — triaging that cause before the next round, or splitting an independent cluster into its own pull request, tends to end the loop faster than fixing them one at a time. Batching the remaining fixes and verifying them before the next push keeps the loop from re-deriving the same set; this PR's reviews already resolve to a critical posting floor. (Observation only — nothing was withheld from this review because of this observation.)
Mechanism health: this round did not close cleanly, so it withholds the incremental anchor — and the round it recovered had no anchor this round could use either — none at all, one with no certifier, one certified by an identity other than the one this round runs under, or one this round's fetch refused or resolved to the head — so the next review re-reads the whole diff unless recovery grafts an earlier own anchor that the round running it can use onto the complete work list this round leaves behind, and keeps doing so until a round's marker carries an anchor again or a graft lands that the round running it can use. (Stated, not acted on — this changes nothing about what the round posts.)
中文说明
仅完成部分审查,审查缺口已披露。
本轮确认的 14 条建议级发现已在 PR 上报告过,不再重复发布(列表见上方英文部分)。
未审查:build-and-test — Test (macos-latest, Node 22.x) and Test (windows-latest, Node 22.x) were skipped in CI at this commit (they do not run on pull_request events); the two changed suites ran locally on Linux/bash 5.2 (345 and 203 passed), so the lanes' native bash 3.2 behaviour could not be exercised。
未审查:the executable-script lint — .github/scripts/autofix-push-and-report.sh: shellcheck is not installed。
未审查:the executable-script lint — .github/scripts/run-autofix-review-verification.sh: shellcheck is not installed。
未探索到全部深度(达到工具调用预算):"Context: PR 10188 adds two deterministic gates to the…":executed merge-freight fixtures — the merge/ kept /fast-forward discriminators in brief item 4 were traced analytically only, no real merge repo was built.;"Context: PR 10188 adds two deterministic gates to the…":line-by-line read of the diff's ~5600-line test chunk (diff lines 1520–7143) — searched by grep and read at its boundaries, the JSX-decoy test, and the negative…;chunk 7:none — but note I could not execute the suite through vitest itself ( node_modules absent in this review worktree); every behavioural claim above comes from ru…。
未审查:反向审计——评审时间预算不足,未能开始第 3 轮。
未检查(工具限制,非阻断):the executable-script lint — .github/workflows/qwen-autofix.yml: actionlint embedded-shell source mapping is not yet supported — not linted。
收敛姿态下延后(第 19 轮,非阻断)——已记录,本轮不要求修改;其中 2 条 Critical 按其失败方向与对照基线延后——fails-closed 且 new-surface:未认证任何错误结果,且 merge base 既无该功能面也无该缺陷——作为后续工作记录在 findings 工件中:共 18 条(原文未翻译,列表见上方英文部分)。
收敛情况:第 19 轮发布了 9 条行内评论,其中 4 条是首次提出;上一轮发布了 10 条(其中 2 条首次提出)。发现反复回到同一批文件:.github/scripts/run-autofix-review-verification.sh(第 8、10、14、15、18 轮已出过发现,本轮又有 4 条)。新发现的产出速度没有下降。一个不断再生兄弟发现的簇,通常意味着逐条修复只在处理同一根因的实例——先定位并处理该根因,或把独立的簇拆成单独的 PR,通常比逐条修复更快结束循环。把剩余修复攒成一批、验证后再推送,可以避免循环反复推导同一组发现;本 PR 的评审已解析为 critical 发布下限。(仅为观察——本轮评审未因此扣留任何内容。)
机制健康:本轮未能干净收尾,因而扣留了增量锚点,而它恢复到的那一轮也没有留下本轮可用的锚点——要么完全没有、要么没有认证者、要么由本轮运行身份之外的身份认证、要么被本轮的获取拒绝或解析为头提交——因此下一次评审将重读整个 diff,除非恢复流程把本轮能使用的更早自有锚点嫁接到本轮留下的完整工作清单上;并会一直如此,直到某一轮的标记重新带上锚点,或落地的嫁接能被运行该轮的评审使用。(仅陈述,不据此行动——这不改变本轮发布的任何内容。)
— qwen3.8-max-2026-09-02 via Qwen Code /review (v0.22.3)
) Three of the four measured defects were false rejects the gate published as machine-measured fact, and the fourth was test surface it never saw. The matcher-tail census counted every member call in a stripped blob, so deleting a spy and its restore read as three removed assertion lines; scope it to continuation-shaped lines, the only shape that arm exists to see. A fast-forwarded main recorded a recount base only for files it added, so main's own delta was charged to a round that synced and then renamed a symbol; record bases for modified files too and read the ledger instead of set membership. The body-call skip arm charged vitest's condition-valued dynamic skip, which is the runtime spelling of the .skipIf environment guard the doctrine already exempts. And `*_tests.rs` matched no pathspec arm, leaving two tracked Rust test files invisible. Each fix carries a witness that a mutation probe confirms goes red without it; the pinned guards for all four arms stay green.
|
🤖 Addressed the latest review feedback (round 14/100). What changed, and what I pushed back on: · 已处理最新评审反馈(第 14/100 轮)。改动内容与我反驳保留之处如下: Round summary — PR #10188 (review round 20, Critical-only window)Four of the nine Critical findings are fixed in code this round, each with its Fixed in codeR14-1 — R14-3 — R18-2 — R8-1, false-reject half — Not fixedR15-1 — R10-3 — R10-2 ( The decision this needs from a maintainerThese four are not four defects. They are one: the test-weakening gate Measured this round, each proposed arm-level escape is closed:
Three directions are available, and choosing between them is a cost and
Recommendation: (c) now, (a) as its own PR. The reporter comparison is a Question for the maintainer: which of (a), (b), (c) should the gate take? Failed checks
GrowthThis round adds 46 net source lines and 157 net test lines. The window was at VerificationEvery command below was actually run in this checkout.
Mutation probes (each new guard has its own witness)Each fix was removed or reverted in place, the named test re-run, and the fix
Direct probes on the shipped helpers
中文说明轮次摘要 —— PR #10188(第 20 轮审查,Critical-only 窗口)九条 Critical 中有四条本轮已在代码中修复,每条都配有自己的见证用例,并用变异探针证明「去掉该修复后见证会变红」。其余五条未修复:一条是因为审查者提出的两个方向都已探针验证、均无法按原样落地;四条是因为它们同属一个根因,其解决方式属于维护者的决定,而不是再加一条 arm。该决定在末尾明确列出。 已在代码中修复R14-1 —— R14-3 —— R18-2 —— R8-1 的误拒绝一半 —— 未修复R15-1 —— R10-3 —— R10-2( 本事项需要维护者做出的决定这四条不是四个缺陷,而是同一个:测试弱化门测量的是断言文本是否存在,从不测量它是否执行,并且它是用正则与一个字符状态 awk 剥离器来做这件事的。第 14、15、18、19 轮都在这些相同位置产出了 fix-induced Critical——本轮九条中有三条标着 本轮实测表明,每一条 arm 级的出路都已被封死:
有三个可选方向,在它们之间做选择属于代价与架构的决定,不是我的:
建议:现在选 (c),把 (a) 作为独立 PR。 reporter 比较是另一件测量工具,值得自己的审查和自己的 lane 预算;本 PR 已进行到第 19 轮,其计数窗口的测试行数预算已经用超,而您自己的收敛观察也指向同一方向(「把独立的簇拆成单独的 PR 通常更快结束循环」)。(b) 不应被选择——它用一个常态化的 fail-closed 缺口换掉一个 fail-open 缺口,而这正是 R14-1 刚刚拒绝过的交易。 给维护者的问题: 本门应采用 (a)、(b)、(c) 中的哪一个?在得到答复之前,这四个 thread 保持开放,且不会再向 失败的检查
增长本轮新增 46 行净源码与 157 行净测试。窗口原本为源码 99 / 测试 283(预算 400/400),因此测试行数超出预算。增长完全来自发现本身所要求的见证覆盖——四项修复对应五个新测试,外加一个「收窄过度」护栏——生产逻辑除四项修复外没有其他改动。在此记录,以便下一轮的增长审计从这个数字开始,而不是重新推导。没有任何删除,因此不涉及删除测试的 advisory。 验证以下每条命令都在本 checkout 中真实执行过。
变异探针(每个新守卫都有自己的见证)每一项修复都被就地移除或还原、重跑指定测试、然后恢复;每次探针之后都确认门脚本逐字节相同。
针对已发布辅助函数的直接探针
Deferred non-Critical feedbackCritical-only mode is active: the round counter reached 5 (this window was seeded at round 10 by 中文说明已进入仅处理 Critical 的模式:轮次计数已达 5(本窗口由 Base-conflict check · 基分支冲突检查: no conflict with main. · 与 main 无冲突。 🧵 Resolved all 3 selected review thread(s). · 已关闭全部选中的 3 条评审线程。 Re-review when you have a moment. After round 100 this bot stops and leaves the PR for a human. · 有空请复审;第 100 轮后本 bot 停止并将 PR 交给人工。 🧠 Handled by Qwen Code · model/模型 |
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
Partially reviewed — gaps disclosed.
9 Suggestion-level finding(s) this review confirmed are already reported on this PR and are not repeated:
- X20-1 CHECK_STATE green is the default arm, so CANCELLED/SKIPPED/STARTUP_FAILURE/EXPECTED classify green — already reported as R1-18 (comment 3869934706)
- X20-2 salvage-merged push stamps pre=green measured on the pre-race head — already reported as R1-4 (comment 3866778394)
- X20-3 regress/repair alternation still unbraked, and the four-adjacent-charged-pushes fixture pins a state the producer cannot emit — already recorded in round 19's deferral list (review 5088294505)
- X20-4 host-probe pin list omits seven gated mapfile-crossing flows, one structurally unlistable — already reported (comment 3878061616)
- X20-5 test-weakening.json missing from the Show run artifacts enumeration — already recorded in round 19's deferral list (review 5088294505)
- X20-6 SKILL.md enumerates three disable spellings while WEAKEN_SKIP_RE charges five families — already recorded in round 19's deferral list (review 5088294505)
- X20-7 per-line grep freight filters are O(lines x blob bytes) inside a step capped at timeout-minutes 60 — already recorded in round 19's deferral list (review 5088294505)
- X20-8 position-free toContain pin on the push-marker capture is satisfied by LOCAL_PUSHED_HEAD — already reported as R1-11 (comment 3869934695)
- X20-9 whole-token round-match fixture oriented backwards, spaceless mutant survives — already recorded in the round-2 deferral list (review 5038649207)
Not reviewed: build-and-test — Test (macos-latest, Node 22.x) and Test (windows-latest, Node 22.x) were skipped in CI at this commit (they do not run on pull_request events), so the bash-3.2 lane the new host-probe gating exists to protect was never exercised; the two changed suites ran locally on Linux/bash 5.2 (553 tests passed).
Not reviewed: reverse audit — stopped after round 3 without two consecutive dry rounds: rounds 1, 2 and 3 each reported findings, so the loop did not converge and the remaining cap rounds (4 and 5 of 5) were not run.
Not reviewed: the executable-script lint — .github/scripts/autofix-push-and-report.sh: shellcheck is not installed.
Not reviewed: the executable-script lint — .github/scripts/run-autofix-review-verification.sh: shellcheck is not installed.
Not explored to full depth (tool budget reached): "agent 8a": did not read diff lines 1401-7330 (the bulk of scripts/tests/qwen-autofix-workflow.test.js ) — only the regression-accounting and marker-stamping describes, so…; "agent 8a": did not walk the weaken_strip_code / weaken_strip_comments / matcher-tail census internals line by line (diff lines 735-1000 read, the awk lexer at diff 456….
Not linted (tool limitation, not a blocker): the executable-script lint — .github/workflows/qwen-autofix.yml: actionlint embedded-shell source mapping is not yet supported — not linted.
Deferred under the convergence posture (round 20, not a blocker) — recorded, not requested in this round; 3 Critical(s) among them are deferred by their axes — fails-closed on new surface, where no wrong result is certified and the merge base had neither the surface nor the defect — and remain follow-up work recorded in the findings artifact:
.github/scripts/autofix-push-and-report.sh:651 — [probe] Critical [fails-closed] [new-surface] pre= is a one-shot premise: a round dispatched while the previous head's checks are still in flight both misses that regression and supersedes it….github/scripts/run-autofix-review-verification.sh:1872 — [probe] Critical [fails-closed] [new-surface] a byte-identical git mv rename of a pre-existing test is charged as deleting every assertion it contains, because --no-renames decompose….github/scripts/autofix-push-and-report.sh:651 — [probe] Critical [fails-closed] [new-surface] both af-155 markers stamp the matrix WINDOW, but a supersede-exempt conflict round acts under a stale key, so the brake is inert in both directio….github/scripts/run-autofix-review-verification.sh:2070 — [probe] the measurement UNAVAILABLE disclosure is the elif of the charged-paths branch, so it is suppressed exactly when measurement went unavailable and the deletion arm still charg…scripts/tests/qwen-autofix-workflow.test.js:17846 — [probe] all 15 marker-bearing fixtures ride a push headline, so nothing pins that the walk reads autofix-regression from a failure or no-op comment; tightening that jq keeps 350/350 green.github/scripts/autofix-push-and-report.sh:630 — [probe] the new posted regression note has no test at all and is absent from the bilingual pair table that pins every other wrapper line this script posts.github/scripts/run-autofix-review-verification.sh:2065 — [review] the advisory sanitization pipeline is pinned only at its comment-marker arm; the three details/summary substitutions and the 300-byte reason cap have no test.github/scripts/run-autofix-review-verification.sh:1152 — [probe] nothing pins that WEAKEN_PATHSPEC is a superset of TEST_PATHSPEC, the invariant the gate's own header states; the invariant holds today, so this is drift coverage.github/scripts/run-autofix-review-verification.sh:2011 — [probe] no test drives a malformed test-weakening.json although the gate claims fail-closed shape behaviour and the sibling ack file has an extensive shape-fuzz setscripts/tests/qwen-autofix-workflow.test.js:24341 — [probe] three fixtures bypass fixtureWrite on a stated premise that is false; the helper reproduces all three byte-for-byte.github/scripts/run-autofix-review-verification.sh:1675 — [probe] the whole-blob skip-marker backstop reads the string-blanked view, so a computed-accessor disable is accepted end to end while the dotted spelling is rejected.github/scripts/run-autofix-review-verification.sh:1640 — [probe] the add-side skip census reads the string-preserving view, so an added string naming a marker is charged; 42 such spellings sit in this PR's own test filescripts/tests/qwen-autofix-workflow.test.js:18046 — [probe] the new terminal-headline cause clause is pinned in English only; deleting its Chinese twin keeps the whole file green while the identical English drop is caughtscripts/tests/qwen-autofix-workflow.test.js:27963 — [probe] no writer-to-scanner round-trip for the new marker although two sibling markers have one; adding an emitter field keeps every test green while af-155 would die silentlyscripts/tests/workflow-size.test.js:939 — [probe] both CommonMark closer rules the header comment states have zero witnesses; each can be deleted with 203/203 green and the real doc cannot substitutescripts/tests/workflow-size.test.js:928 — [probe] two of the three unmodeledFences arms (blockquote, four-column indent) have no witness; four mutants survive the whole suitescripts/tests/qwen-autofix-workflow.test.js:28054 — [probe] the own-lane exclusion is the third hand-copy of the five-name list and pins only 2 of 5 names; dropping Qwen CI Failure Patrol keeps every test greenscripts/tests/qwen-autofix-workflow.test.js:28106 — [probe] the charge path is pinned against green and red heads only; relaxing the red conjunct to not-green keeps all eight assertions green and would charge an in-flight headscripts/tests/qwen-autofix-workflow.test.js:25459 — [probe] runGate never validates the weaken fixture key, so a renamed negative-control key degrades to an empty round and its guard goes silent with no red anywherescripts/tests/qwen-autofix-workflow.test.js:26025 — [probe] the bash-4 boundary pin hand-lists one construct out of seven; 165 runGate call sites are protected by 28 host-probe gates on a lane that never runs on pull_request- …and 1 more (see the run report)
Convergence: round 20 posted 13 inline comment(s), 6 of them reported for the first time; the previous round posted 9 (4 new). Findings keep coming back to the same files: .github/scripts/run-autofix-review-verification.sh (findings in rounds 8, 10, 14, 15, 18, 19; 4 more now). The rate of new findings is not falling. A cluster that keeps producing siblings usually means the fixes are treating instances of a shared root cause — triaging that cause before the next round, or splitting an independent cluster into its own pull request, tends to end the loop faster than fixing them one at a time. Batching the remaining fixes and verifying them before the next push keeps the loop from re-deriving the same set; this PR's reviews already resolve to a critical posting floor. (Observation only — nothing was withheld from this review because of this observation.)
Mechanism health: this round did not close cleanly, so it withholds the incremental anchor — and the round it recovered had no anchor this round could use either — none at all, one with no certifier, one certified by an identity other than the one this round runs under, or one this round's fetch refused or resolved to the head — so the next review re-reads the whole diff unless recovery grafts an earlier own anchor that the round running it can use onto the complete work list this round leaves behind, and keeps doing so until a round's marker carries an anchor again or a graft lands that the round running it can use. (Stated, not acted on — this changes nothing about what the round posts.)
Residual risk: this loop is persistently critical — Criticals stood in the previous round's work-list and stand again this round (13 Critical(s)), the rate of first-time findings is not falling (this round 6, previous 4), and the standing Critical backlog is not shrinking. The severity floor will not converge it. Recommendation: land-with-residual-risk — the exit is a maintainer risk-acceptance decision (merge, carrying the residual risk), not another review round. Residual-risk inventory for that decision (maintainer to complete):
| standing Critical | attack surface | attacker-dependency | blast radius |
|---|---|---|---|
| (each standing Critical) | … | … | … |
Advisory only — it does not block this review.
中文说明
仅完成部分审查,审查缺口已披露。
本轮确认的 9 条建议级发现已在 PR 上报告过,不再重复发布(列表见上方英文部分)。
未审查:build-and-test — Test (macos-latest, Node 22.x) and Test (windows-latest, Node 22.x) were skipped in CI at this commit (they do not run on pull_request events), so the bash-3.2 lane the new host-probe gating exists to protect was never exercised; the two changed suites ran locally on Linux/bash 5.2 (553 tests passed)。
未审查:reverse audit — stopped after round 3 without two consecutive dry rounds: rounds 1, 2 and 3 each reported findings, so the loop did not converge and the remaining cap rounds (4 and 5 of 5) were not run。
未审查:the executable-script lint — .github/scripts/autofix-push-and-report.sh: shellcheck is not installed。
未审查:the executable-script lint — .github/scripts/run-autofix-review-verification.sh: shellcheck is not installed。
未探索到全部深度(达到工具调用预算):"agent 8a":did not read diff lines 1401-7330 (the bulk of scripts/tests/qwen-autofix-workflow.test.js ) — only the regression-accounting and marker-stamping describes, so…;"agent 8a":did not walk the weaken_strip_code / weaken_strip_comments / matcher-tail census internals line by line (diff lines 735-1000 read, the awk lexer at diff 456…。
未检查(工具限制,非阻断):the executable-script lint — .github/workflows/qwen-autofix.yml: actionlint embedded-shell source mapping is not yet supported — not linted。
收敛姿态下延后(第 20 轮,非阻断)——已记录,本轮不要求修改;其中 3 条 Critical 按其失败方向与对照基线延后——fails-closed 且 new-surface:未认证任何错误结果,且 merge base 既无该功能面也无该缺陷——作为后续工作记录在 findings 工件中:共 21 条(原文未翻译,列表见上方英文部分)。
收敛情况:第 20 轮发布了 13 条行内评论,其中 6 条是首次提出;上一轮发布了 9 条(其中 4 条首次提出)。发现反复回到同一批文件:.github/scripts/run-autofix-review-verification.sh(第 8、10、14、15、18、19 轮已出过发现,本轮又有 4 条)。新发现的产出速度没有下降。一个不断再生兄弟发现的簇,通常意味着逐条修复只在处理同一根因的实例——先定位并处理该根因,或把独立的簇拆成单独的 PR,通常比逐条修复更快结束循环。把剩余修复攒成一批、验证后再推送,可以避免循环反复推导同一组发现;本 PR 的评审已解析为 critical 发布下限。(仅为观察——本轮评审未因此扣留任何内容。)
机制健康:本轮未能干净收尾,因而扣留了增量锚点,而它恢复到的那一轮也没有留下本轮可用的锚点——要么完全没有、要么没有认证者、要么由本轮运行身份之外的身份认证、要么被本轮的获取拒绝或解析为头提交——因此下一次评审将重读整个 diff,除非恢复流程把本轮能使用的更早自有锚点嫁接到本轮留下的完整工作清单上;并会一直如此,直到某一轮的标记重新带上锚点,或落地的嫁接能被运行该轮的评审使用。(仅陈述,不据此行动——这不改变本轮发布的任何内容。)
残余风险:本循环处于 persistently-critical 形态——上一轮工作清单中的 Critical 本轮依然存在(本轮 13 条 Critical),首次发现的速率没有下降(本轮 6,上一轮 4),且未决 Critical 积压没有减少。severity floor 无法使其收敛。建议:land-with-residual-risk——出口是 maintainer 的风险接受决定(合入并承担残余风险),而非再开一轮评审。供该决定使用的残余风险清单(maintainer 填写):按每条未决 Critical 列出「攻击面 · 攻击者依赖性 · 影响范围」三栏。仅为建议——不阻断本次评审。
— qwen3.8-max-2026-09-02 via Qwen Code /review (v0.22.3)
|
🤖 Could not produce a passing fix for this feedback (round 15/100). This item now needs a human; the loop stays engaged and still picks up new feedback and base conflicts, but will not retry this item on its own. What I found before stopping: Growth-audit handoff — PR #10188, round 21: which authority the test-weakening gate should rest onThis round stopped at the growth audit and changed nothing. Verdict: conflict The decision I need from youThe test-weakening gate this PR adds is 1045 new lines in The decision is which authority the gate should measure test coverage with, Why this is a judgment about structure, not about diligence
中文说明🤖 未能为该反馈产生可通过验证的修复(第 15/100 轮)。此项现在需要人工处理;循环保持在线,仍会拾取新反馈与 base 冲突,但不会自行重试此项。 Run log: https://github.com/QwenLM/qwen-code/actions/runs/33664394589 🧠 Handled by Qwen Code · model/模型 |
…us (#10188) Replace the awk lexer, the four extended regexes, the matcher-tail census, the per-commit accumulators, the verdict-time recount and the netting skip with one instrument: count-test-surface.mjs parses the whole file with the TypeScript compiler and reports its declared test surface (statement-level assertion chains, test/describe registrations with their enabled state, bare early returns ahead of assertions). Each file's round delta is tip - pre-round - main's contribution, where main's contribution across a merge is measured on git's own auto-merge of main's side (git merge-file --ours) and across a fast-forwarded main commit on the commit itself, so the same weakening measures the same before, during or after a merge, and main's delta neither charges nor shields. Narrow the pathspec to test-NAMED files (a README under integration-tests/ is not a test deletion), read the enumeration producers' status instead of swallowing it, re-apply the reason bar when rendering, and state the declared non-goals in the gate header: reachability, condition-valued guards and by-reference options are runtime facts the runner-backed instruments judge. Bind the af-155 regression classifier to the commit its rollup describes (headRefOid read with the rollup; a moved head classifies none), exclude the loop's own dispatch-pending StatusContext by its exact context value, classify verdict-less checks (EXPECTED, STALE, STARTUP_FAILURE) as pending, stamp pre=none after a salvage-merged push, drop the unread CHECK_STATE from the failure report's env, and render the regression disclosure on every report shape. Drop the hand-rolled CommonMark fence model from workflow-size.test.js: the existing anchor/pointer/contents pins already prove navigation, and a text model of a renderer is the class of instrument this change removes.
Maintainer decision on the round-21 growth-audit handoff — structural rewrite landed in d0bde3dThe handoff asked which authority the test-weakening gate should rest on. Decision: the TypeScript compiler's parser, for the declared test surface ( What went away. The awk lexer,
The 13 standing Criticals. Closed by replacing the instrument: R8-1, R10-2 (textual half), R14-1, R14-3, R15-1, R18-1, R20-1. Fixed as bounded defects: R20-2 (pathspec narrowed to test-named files), R20-3 (dispatch StatusContext excluded by exact context), R20-4 (rollup bound to Declared non-goals (in the gate header and Verification. Threads. All 159 unresolved review threads carry a reply naming their disposition (closed by the new instrument / fixed / by design / out of scope) and the test that pins it, and are resolved. The 中文说明对第 21 轮 growth-audit 交接问题的维护者决定——结构性重写已在 d0bde3d 落地交接问题是「测试弱化门应以什么为权威」。决定:以 TypeScript 编译器的解析器为权威,计量「声明的测试面」( 删掉了什么。 awk 词法器、三条正则与 matcher-tail 普查、逐 commit 累加器、判定期重算、重算基线台账、字节相同抵消——过去十一轮为了用文本模式逼近解析器而加的全部机制。合并归因改为
13 条 standing Critical。 换仪器关闭:R8-1、R10-2(文本半边)、R14-1、R14-3、R15-1、R18-1、R20-1。作为有界缺陷修复:R20-2(pathspec 收窄到测试命名文件)、R20-3(按精确 context 排除 dispatch StatusContext)、R20-4(rollup 绑定 声明的非目标(写在门头注释与 验证。 线程。 159 条未决评审线程全部回帖说明处置(换仪器关闭 / 已修 / 按设计 / 范围外)并指出钉住它的用例,且已 resolve。 |
|
CI note on d0bde3d: the 中文说明关于 d0bde3d 上的 CI: |
|
Qwen Code review did not complete successfully. The review pipeline failed before a review could be posted. A transient error is retried automatically; if you are seeing this, retry with |
|
🔀 Base updated: red check(s) [Test (ubuntu-latest, Node 22.x), Lint & Static (ubuntu-latest, Node 22.x), review-pr, web-shell E2E Smoke (ubuntu-latest, Node 22.x)] pass on current main — merged current main via update-branch; CI will re-run. 中文说明🔀 已更新 base:红色检查 [Test (ubuntu-latest, Node 22.x), Lint & Static (ubuntu-latest, Node 22.x), review-pr, web-shell E2E Smoke (ubuntu-latest, Node 22.x)] 在当前 main 上通过 —— 已通过 update-branch 合入当前 main,CI 将重新运行。 |
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
Partially reviewed — gaps disclosed.
Not reviewed: executable-script lint — .github/scripts/autofix-push-and-report.sh: shellcheck is not installed.
Not reviewed: executable-script lint — .github/scripts/run-autofix-review-verification.sh: shellcheck is not installed.
Not reviewed: build-and-test — Test (macos-latest, Node 22.x) and Test (windows-latest, Node 22.x) do not run on pull_request events; the changed suite ran on Linux only (277/277).
Not reviewed: test-efficacy probe — harness control never ran (harnessValidated: null; 0 revert/mutant/hunk probes), so mutation-level test coverage is unverified by the probe.
Not reviewed: the executable-script lint — .github/scripts/autofix-push-and-report.sh: shellcheck is not installed.
Not reviewed: the executable-script lint — .github/scripts/run-autofix-review-verification.sh: shellcheck is not installed.
Not linted (tool limitation, not a blocker): .github/workflows/qwen-autofix.yml — actionlint embedded-shell source mapping is not yet supported.
Deferred under the convergence posture (round 21, not a blocker) — recorded, not requested in this round; 2 Critical(s) among them are deferred by their axes — fails-closed on new surface, where no wrong result is certified and the merge base had neither the surface nor the defect — and remain follow-up work recorded in the findings artifact:
.github/workflows/qwen-autofix.yml:3875 — [probe] Critical [fails-closed] [new-surface] staging cp lacks both the pre-merge absence tolerance (|| true) and the af-149 planted-leftover guard (rm -rf): pre-merge PR-event rounds die at staging….github/scripts/count-test-surface.mjs:332 — [probe] Critical [fails-closed] [new-surface] guards signal is a position-free net delta with no baseline attribution, so guards contributed by a round-ADDED registration reject an honest additio…scripts/tests/qwen-autofix-workflow.test.js:25285 — [probe] bash-3.2 static test boundary relocates onto an in-gate mapfile and the test stays greenscripts/tests/qwen-autofix-workflow.test.js:26237 — [probe] af-155 marker-emitter pins survive branch-move and PUSH_PRE-blank mutations.github/scripts/run-autofix-review-verification.sh:1175 — [probe] the add/add and modify/delete weaken_auto_blob arms have no fixture.github/scripts/run-autofix-review-verification.sh:1303 — [probe] UNAVAILABLE enumeration swallows its producer's exit status inside a process substitutionscripts/tests/qwen-autofix-workflow.test.js:25306 — [probe] byte-safe accepted half asserts neither exit status nor rendered advisory; sanitizer mutation survives greenscripts/tests/qwen-autofix-workflow.test.js:25289 — [probe] bash-3.2 pin regex misses nine bash>=4 construct classesscripts/tests/qwen-autofix-workflow.test.js:25668 — [probe] DISABLING's fails/failing members have no witness rowscripts/tests/qwen-autofix-workflow.test.js:25857 — [probe] >=2-event accumulation (incl. disable->re-enable reset) is pinned by nothingscripts/tests/qwen-autofix-workflow.test.js:26214 — [probe] PUSH_RACE_MERGED carve-out pinned only by shape; reorder mutation ships greenscripts/tests/qwen-autofix-workflow.test.js:25178 — [probe] host-gate meta-test omits the byte-safe test, the only ungated accepted-round testscripts/tests/qwen-autofix-workflow.test.js:25853 — [probe] measure guards delta pinned only ever-zero; dropping the subtraction ships greenscripts/tests/qwen-autofix-workflow.test.js:26043 — [probe] classifier suite exercises 2 of 5 own-lane names and 1 of 5 red conclusions.github/scripts/run-autofix-review-verification.sh:1334 — [probe] 40-character ack reason floor counts zero-width characters; a visually empty reason passesscripts/tests/qwen-autofix-workflow.test.js:25884 — [probe] brand-new-file baselinePresent exemption unpinned (corrected witness: mutation pre!==null || tip!==null ships green)scripts/tests/qwen-autofix-workflow.test.js:25825 — [probe] nothing pins that a failed measure invocation exits non-zero, the UNAVAILABLE routing's sole premisescripts/tests/qwen-autofix-workflow.test.js:18228 — [probe] whole-number round-match pin oriented the way a substring mutation survives
Convergence: round 21 posted 10 inline comment(s), 9 of them reported for the first time; the previous round posted 13 (6 new). Findings keep coming back to the same files: .github/scripts/run-autofix-review-verification.sh (findings in rounds 8, 10, 14, 15, 18, 19, 20; 4 more now); .github/workflows/qwen-autofix.yml (findings in round 20; 1 more now). The rate of new findings is not falling. A cluster that keeps producing siblings usually means the fixes are treating instances of a shared root cause — triaging that cause before the next round, or splitting an independent cluster into its own pull request, tends to end the loop faster than fixing them one at a time. Batching the remaining fixes and verifying them before the next push keeps the loop from re-deriving the same set; this PR's reviews already resolve to a critical posting floor. (Observation only — nothing was withheld from this review because of this observation.)
Mechanism health: this round did not close cleanly, so it withholds the incremental anchor — and the round it recovered had no anchor this round could use either — none at all, one with no certifier, one certified by an identity other than the one this round runs under, or one this round's fetch refused or resolved to the head — so the next review re-reads the whole diff unless recovery grafts an earlier own anchor that the round running it can use onto the complete work list this round leaves behind, and keeps doing so until a round's marker carries an anchor again or a graft lands that the round running it can use. (Stated, not acted on — this changes nothing about what the round posts.)
中文说明
仅完成部分审查,审查缺口已披露。
未审查(原文为英文):executable-script lint — .github/scripts/autofix-push-and-report.sh: shellcheck is not installed.
未审查(原文为英文):executable-script lint — .github/scripts/run-autofix-review-verification.sh: shellcheck is not installed.
未审查(原文为英文):build-and-test — Test (macos-latest, Node 22.x) and Test (windows-latest, Node 22.x) do not run on pull_request events; the changed suite ran on Linux only (277/277).
未审查(原文为英文):test-efficacy probe — harness control never ran (harnessValidated: null; 0 revert/mutant/hunk probes), so mutation-level test coverage is unverified by the probe.
未审查(原文为英文):the executable-script lint — .github/scripts/autofix-push-and-report.sh: shellcheck is not installed.
未审查(原文为英文):the executable-script lint — .github/scripts/run-autofix-review-verification.sh: shellcheck is not installed.
未检查(工具限制,非阻断):.github/workflows/qwen-autofix.yml——actionlint 对 workflow 内嵌 shell 的源映射尚未支持。
收敛姿态下延后(第 21 轮,非阻断)——已记录,本轮不要求修改;其中 2 条 Critical 按其失败方向与对照基线延后——fails-closed 且 new-surface:未认证任何错误结果,且 merge base 既无该功能面也无该缺陷——作为后续工作记录在 findings 工件中:共 18 条(原文未翻译,列表见上方英文部分)。
收敛情况:第 21 轮发布了 10 条行内评论,其中 9 条是首次提出;上一轮发布了 13 条(其中 6 条首次提出)。发现反复回到同一批文件:.github/scripts/run-autofix-review-verification.sh(第 8、10、14、15、18、19、20 轮已出过发现,本轮又有 4 条);.github/workflows/qwen-autofix.yml(第 20 轮已出过发现,本轮又有 1 条)。新发现的产出速度没有下降。一个不断再生兄弟发现的簇,通常意味着逐条修复只在处理同一根因的实例——先定位并处理该根因,或把独立的簇拆成单独的 PR,通常比逐条修复更快结束循环。把剩余修复攒成一批、验证后再推送,可以避免循环反复推导同一组发现;本 PR 的评审已解析为 critical 发布下限。(仅为观察——本轮评审未因此扣留任何内容。)
机制健康:本轮未能干净收尾,因而扣留了增量锚点,而它恢复到的那一轮也没有留下本轮可用的锚点——要么完全没有、要么没有认证者、要么由本轮运行身份之外的身份认证、要么被本轮的获取拒绝或解析为头提交——因此下一次评审将重读整个 diff,除非恢复流程把本轮能使用的更早自有锚点嫁接到本轮留下的完整工作清单上;并会一直如此,直到某一轮的标记重新带上锚点,或落地的嫁接能被运行该轮的评审使用。(仅陈述,不据此行动——这不改变本轮发布的任何内容。)
— qwen3.8-max via Qwen Code /review (v0.23.0)
| for (const k of before.enabledTitles) { | ||
| if (!afterEnabled.has(k)) mainDisabled.add(k); | ||
| } |
There was a problem hiding this comment.
[Critical] R21-1: [certifies-falsely] [new-surface] Class-level finding: measure() attributes by title-keyed Set membership and position-free net deltas with no per-registration identity, multiplicity, or event pairing — seven demonstrated entrances of one structural root, each probed against this commit. (1) mainDisabled is add-only across events: main disables then re-enables a title, the stale shield survives forever, and the round disabling it afterwards escapes with newlyDisabled:[] when it adds one balancing test. (2) enabled counts describe/suite wrappers like tests: flattening a wrapper measures enabled:-1 and rejects an honest round with "enabled test registration(s) removed". (3) newlyDisabled matches position-free kind:title keys: a brand-new it.todo('a') beside a baseline-enabled it('a') is charged as disabling a pre-existing test. (4) The shield is title-granular, not multiplicity-aware: with two same-title registrations, main disabling ONE records no shield and the round is charged for main's act; adjacent states fabricate newlyDisabled on a pure addition and report 2 disabled when the round disabled 1. (5) The events loop adds ALL of each merge event's after.enabledTitles to baselineEnabled — the merge's after is auto-merged onto the branch side, so the round's own mid-round registrations leak into the baseline and their later disabling is charged as pre-existing. (6) An event's negative delta is credited unconditionally: main deletes 5, the round restores those 5 and deletes 5 OTHERS, (5−10)−(5−10)=0 shields the round's own weakening (full gate: status=0, outcome=fixed). (7) Renamed-then-disabled escapes: it.skip('a, renamed') plus one balancing new test measures assertions:1, enabled:0, newlyDisabled:[] and ships. Entrances 1, 6, 7 let a weakening round pass with no evidence; 2, 3, 4, 5 reject honest rounds or force false acknowledgements that render as machine-measured fact. A fix at the algebra closes all seven; fixing them entrance by entrance repeats the round-12-to-20 lineage this PR's own history shows.
Witness:
measure() at the reviewed commit, per entrance:
(1) stale-shield escape: {"assertions":0,"guards":0,"enabled":0,"newlyDisabled":[]}
with mainDisabled.delete(k) fix: newlyDisabled:["test:a"]
(2) wrapper flatten: {"enabled":-1,"newlyDisabled":[]} / kind==='test' fix: enabled:0, describe-skip still charges
(3) title collision: new it.todo('a') beside enabled it('a') -> newlyDisabled:["test:a"] / multiset fix: []
(4) duplicate-title: main disables one of two it('a') -> newlyDisabled:["test:a"];
round-adds-only with pre-existing disabled twin -> ["test:a"];
main-skips-one/round-skips-other -> ["test:a","test:a"] (charge text says 2, round disabled 1)
(5) after-leak: round's own mid-round test X, merge event on the file -> newlyDisabled:["test:X"] / beforeEnabled guard: []
(6) negative-delta credit: pre=10, event {before:10,after:5}, tip=5 (restored 5, deleted 5 others) -> {"assertions":0};
full gate: status=0, outcome=fixed, rejection '' (mirror ff-ride-revert charges assertions:-1)
(7) rename+balance: it.skip('a, renamed') + new it('b') -> {"assertions":1,"enabled":0,"newlyDisabled":[]};
title-similarity attribution flips to newlyDisabled:["test:a, renamed"], pure-rename fixture still uncharged
Fix direction: replace the title-set algebra with per-registration, multiplicity-aware attribution — mainDisabled as a Map of key→count built as the multiset difference of before/after enabledTitles per event (re-enabled titles consume shield counts); baselineEnabled extended only with titles main itself introduced (in after but not in before); newlyDisabled computed as the multiset difference of tip-disabled against baseline-disabled and the shield counts; plus a normalized-title-similarity pairing so a renamed-then-disabled registration is attributed to the registration it replaced. The existing constraint at run-autofix-review-verification.sh:1273 reads (.newlyDisabled | length) to build the charge text, so newlyDisabled must stay an array whose length equals the number of registrations charged, and the header contract at count-test-surface.mjs:46-53 ("main's contribution neither charges nor shields") is the acceptance bar. Please pin each arm: extend "subtracts main's contribution across events, and only that" with one scenario per entrance (two-event disable→re-enable asserting newlyDisabled:['test:a']; duplicate-title main-disable asserting []; the leak/credit/rename manifests above); removing any arm of the fix must redden exactly its own scenario while all six existing single-event scenarios stay green.
中文说明
[Critical] R21-1:类级问题。measure() 的归属代数按「标题键的集合成员关系」和「无位置的净差值」计费,没有按注册项做身份、重数或事件配对——同一结构性根因有七个已验证入口(均在本提交上探针复现):(1) mainDisabled 跨事件只增不减:main 先禁用再启用某标题后,陈旧豁免永久留存,轮次随后禁用该测试并补一个新测试即可逃逸(newlyDisabled:[]);(2) enabled 把 describe/suite 包装器当测试计数:拍平包装器测得 enabled:-1,诚实轮次被以「enabled test registration(s) removed」拒绝;(3) newlyDisabled 按无位置的 kind:title 键匹配:在基线已启用的 it('a') 旁新增 it.todo('a') 会被计费为禁用了既有测试;(4) 豁免按标题粒度而非重数:同名注册有两个、main 禁用其一时不记录任何豁免,轮次替 main 的行为被计费;相邻形态还会在纯新增时伪造 newlyDisabled、在「main 禁一个/轮禁另一个」时报告 2 个而轮次只禁了 1 个;(5) 事件循环把每个 merge 事件 after.enabledTitles 全部并入 baselineEnabled——merge 的 after 是在分支侧之上自动合并的结果,轮次自己中途启用的注册因此泄漏进「基线」,其后续禁用被当成禁用既有测试计费;(6) 事件负差值无条件计信:main 删 5 条、轮次恢复这 5 条并另删 5 条,(5−10)−(5−10)=0 把轮次自己的弱化屏蔽掉(整门探针:status=0、outcome=fixed);(7) 改名后禁用可逃逸:it.skip('a, renamed') 加一个平衡新测试测得 assertions:1, enabled:0, newlyDisabled:[] 直接放行。入口 1、6、7 让弱化轮次无证据通过;2、3、4、5 拒绝诚实轮次或逼其写下以「机器测量」口吻发布的虚假承认。在代数根上修复可一次关闭七者;逐入口修补会重演本 PR 历史上第 12–20 轮的血统。
证人证据:见英文版逐入口探针输出(修复前后对照,均在本提交上执行)。
修复方向:以「按注册项、重数感知」的归属取代标题集合代数——mainDisabled 改为键→计数的 Map(按事件做 before/after enabledTitles 的多重集差,被重新启用的标题消耗豁免计数);baselineEnabled 只并入 main 自己引入的标题(在 after 中但不在 before 中);newlyDisabled 按 tip 禁用集对基线禁用集与豁免计数的多重集差计算;并引入归一化标题相似度配对,使「改名后禁用」归属到被替换的注册项。既有约束 run-autofix-review-verification.sh:1273 读取 (.newlyDisabled | length) 生成计费文本,因此 newlyDisabled 必须保持为长度等于被计费注册数的数组;验收标准是 count-test-surface.mjs:46-53 的头部契约(「main 的增量既不计费也不屏蔽」)。请为每个入口补一个「去掉修复就会红」的场景(并入现有 measure 测试),且六个既有单事件场景保持绿色。
— qwen3.8-max via Qwen Code /review (v0.23.0)
| else | ||
| registrations.push({ kind: 'test', title: '', disabled: true, fn: null }); |
There was a problem hiding this comment.
[Critical] R21-2: [certifies-falsely] [new-surface] Class-level finding: count() decides registration, disabling and guard attribution by chain-root identifier name and syntactic shape, with no binding awareness and no control-flow modeling, while the runner-backed instruments the header delegates reachability to read green on exactly these shapes — five high-confidence demonstrated entrances, each probed at this commit, plus three folded low-confidence siblings of the same root. (1) truthyLiteral/falsyLiteral/optionsDisable never peel ParenthesizedExpression/unary wrappers: skipIf((true)), runIf((false)), { skip: (true) }, { todo: (true) }, skipIf(((true))), runIf((0)) all measure ENABLED while vitest's truthiness (@vitest/runner chunk-hooks.js:887-892, 619) skips them. (2) isBodySkip ignores enclosing control flow: an if-guarded ctx.skip() marks the registration DISABLED in the baseline although the test runs whenever the condition is false — real instances at HEAD (packages/cli/src/commands/review/lib/same-file.test.ts, packages/core/src/config/config.test.ts:7195) measure disabled — so making the skip unconditional later measures zero (escape) and introducing the idiom charges an honest round (false charge). (3) enclosingRegistration attributes a skip by pure positional containment across the nested-function boundary the guard walker itself respects: a conditional ctx.skip() inside a forEach callback marks the enclosing test disabled, so its later it.skip conversion measures nothing. (4) A callback passed by reference gets fn: null: early returns or ctx.skip() routed through a referenced helper charge nothing, and an unattributed skip() fabricates the phantom {kind:'test', title:''} registration pushed here. (5) Phantom kind:title keys collide with real ones: a module/hook-level skip(); beside any empty-title baseline registration yields newlyDisabled:['test:'] and rejects a round that touched no test — a population sweep at HEAD found 15 files carrying an empty-title enabled registration (channel-worker-manager.test.ts carries 131, produced by fixture-method call chains like test.manager.setSelection(...) rooted at local it/test identifiers). Folded low-confidence siblings, same root: shadowed it/expect bindings measure byte-identical surface while vitest registers nothing; statically-certain conditions (skipIf(cond || true)) measure as guards; return undefined; counts as no guard. The surface cannot be completed entrance by entrance — every spelling fix reopens on the next JS construct.
Witness:
count()/measure() at the reviewed commit, per entrance:
(1) the six parenthesized/unary forms -> {"enabled":7,"disabled":[]}; vitest authority chunk-hooks.js:887
taskFn.skipIf = function(condition) { return condition ? this.skip : this; } — all six skip at runtime;
peel fix -> {"enabled":1,"disabled":[6 titles]}
(2) guarded ctx.skip() -> disabled:["test:guarded"]; FACE1 guarded->unconditional: {"enabled":0,"newlyDisabled":[]} (escape);
FACE2 running->guarded: {"enabled":-1,"newlyDisabled":["test:a"]} (false charge);
HEAD files: same-file.test.ts / config.test.ts both measure disabled; fix re-measures them enabled
(3) nested forEach ctx.skip(): enclosing test disabled; later it.skip conversion {"enabled":0,"newlyDisabled":[]};
parent-walk fix flips to enabled:1
(4) it('a', body) with early return in body: {"guards":0} vs inline weakening {"guards":1};
helper maybeSkip(ctx) lands in the phantom branch, matching no baseline key
(5) module-scope skip(); + empty-title baseline registration -> {"newlyDisabled":["test:"]};
sweep: scanned 2385 test files, 15 carry an empty-title enabled registration
(channel-worker-manager.test.ts: 131 keys; test.manager.setSelection(selection) at :143 counted as a registration);
deleting one such call measures enabled:-1 end-to-end
Fix direction: stop deciding runtime collection from syntax shape — either make the classification binding- and control-flow-aware (peel wrappers in the literal checks, fold statically-certain constants, attribute body-skips only when unconditional within the body and contained without crossing a function boundary, resolve referenced callbacks one hop, and drop the phantom-registration else-branch above), or ask the runner what it collected and measure against that ground truth. The fix must respect the header contract (count-test-surface.mjs:32-43): a body-level UNCONDITIONAL ctx.skip() must still count as disabled (the repo's same-file.test.ts:94 and config.test.ts:7207 call it directly in the body's control flow), and condition-valued guards remain deliberately not measured. Please pin each arm with a fixture row/scenario (parenthesized literals, if-guarded skip asserting enabled, nested-closure skip, referenced-callback early return, module-scope skip(); asserting newlyDisabled:[]); removing any arm must redden its own scenario while the two real guarded sites keep measuring enabled.
中文说明
[Critical] R21-2:类级问题。count() 仅凭链根标识符名与语法形状判定注册、禁用与守卫归属,不做绑定分析、不建模控制流;而头部注释把可达性委托给的「运行器侧仪器」恰好在这些形态上读绿。五个高置信入口(均在本提交探针复现)加三个同根低置信兄弟形态:(1) 字面量判定不剥 ParenthesizedExpression/一元包装:skipIf((true))、runIf((false))、{ skip: (true) } 等六种写法测得 ENABLED,而 vitest 按真假值(chunk-hooks.js:887-892, 619)全部跳过;(2) isBodySkip 不看外层控制流:if 守卫的 ctx.skip() 把注册在基线中标为 DISABLED,而该测试在条件为假时照常运行——HEAD 上 same-file.test.ts 与 config.test.ts:7195 两处真实用例因此被测为 disabled——之后把跳过改成无条件测得零信号(逃逸),引入该习语又会让诚实轮次被计费(误记账);(3) enclosingRegistration 以纯位置包含归属跳过、跨越了守卫遍历自己都尊重的嵌套函数边界:forEach 回调里的条件 ctx.skip() 会把外层测试标为 disabled,其后的 it.skip 转换因此测不到任何信号;(4) 以引用传入的回调得到 fn: null:经引用助手路由的提前返回/ctx.skip() 不计费,未归属的 skip() 还会制造此处 push 的幻影 {kind:'test', title:''} 注册;(5) 幻影键与真实键碰撞:模块/钩子级 skip(); 遇到任何空标题基线注册即产生 newlyDisabled:['test:'],拒绝一个没碰任何测试的轮次——全仓扫描发现 15 个文件带空标题启用注册(channel-worker-manager.test.ts 有 131 个,源于 test.manager.setSelection(...) 这类以局部 it/test 为根的夹具方法调用链)。同根低置信兄弟:影子化 it/expect 绑定测得逐字节相同的表面而 vitest 什么也不注册;静态恒定条件(skipIf(cond || true))被测成守卫;return undefined; 不算守卫。该检测面无法逐入口补完——每修一种写法都会被下一种 JS 构造重新打开。
证人证据:见英文版逐入口探针输出(修复前后对照)。
修复方向:不要再用语法形状推断运行时收集——要么让分类具备绑定与控制流感知(剥包装、折叠静态恒定常量、仅在「体内无条件且不跨嵌套函数边界」时归属体级跳过、一跳解析引用回调、删除上述幻影注册 else 分支),要么直接询问运行器实际收集到了什么。修复须尊重头部契约(:32-43):体内无条件的 ctx.skip() 仍须计为 disabled(仓库中 same-file.test.ts:94、config.test.ts:7207 是真实用例),条件值守卫仍刻意不测。请为每个入口补钉子,去掉修复任一分支只红对应场景。
— qwen3.8-max via Qwen Code /review (v0.23.0)
| if [[ -z "${p1}" ]]; then | ||
| # The branch holds no blob: main added the file and it lands; or the | ||
| # branch deleted it earlier and main's edit is a modify/delete conflict | ||
| # resolved for the branch's deletion. |
There was a problem hiding this comment.
[Critical] R21-3: [certifies-falsely] [new-surface] A round's own deletion of a pre-existing test file is silently exempted when a main-derived merge follows the deletion and main's side still holds the file. At the merge commit the first-parent diff is quiet (absent vs absent) but the second-parent diff differs, so an event is recorded; weaken_auto_blob hits this p1-empty branch with a non-empty base and returns nothing; the event becomes {before: null, after: null}; count-test-surface.mjs sets baselinePresent = ev.after !== null → false; the shell then takes the "not the round's to weaken" branch and accepts the round with no signal — although the file was held by the pre-round baseline and is absent at the tip, against this section's stated "never on a whole-file deletion" contract.
Witness:
End-to-end through the real runGate harness at the reviewed commit:
PR: CAND2_STATUS=0, outcome=fixed, rejection '' (deletion exempted)
fix ([[ -n "${before}" || -n "${after}" ]] || continue before events+=):
CAND2_STATUS=1, rejection '- pkg/a.test.ts — test file deleted'
four attribution suites (merge-delete-freight, own-authorship,
judges-deletions-when-unavailable, honest-edits) stay green under the fix
Suggested fix — skip recording events where both before and after are empty; they carry no main delta (both sides count as ZERO) and their only effect is the baselinePresent flip:
[[ -n "${before}" || -n "${after}" ]] || continueThe fix must not break count-test-surface.mjs:384 (baselinePresent = ev.after !== null), the only mechanism by which a MAIN deletion exempts a round (pinned by the merge-delete-freight fixture) — so the skip may fire only when before is also empty. Please pin it with a WEAKEN_FIXTURES entry (round git rms F, mainMoves edits F, merge resolved for the deletion) asserting rejection with signal "test file deleted"; removing the skip guard must turn it red.
中文说明
[Critical] R21-3:当轮次自己删除了一个既有测试文件、随后又发生一次 main 派生合并且 main 侧仍持有该文件时,轮次自己的删除会被静默豁免:合并提交上第一父 diff 静默(缺失对缺失)而第二父 diff 有差异,于是记录了一个事件;weaken_auto_blob 走到这个 p1 为空、base 非空的分支并返回空;事件成为 {before: null, after: null};count-test-surface.mjs 置 baselinePresent = ev.after !== null → false;shell 随后走「非本轮可弱化」分支直接接受——尽管该文件在轮次前基线上存在、在 tip 上消失,违反本节「整文件删除绝不放行」的自我契约。
证人证据:用本套件自带的 runGate 真实门端到端执行:原代码 CAND2_STATUS=0、outcome=fixed、无拒绝;加上修复(事件入列前 [[ -n "${before}" || -n "${after}" ]] || continue)后 status=1、拒绝信息点名 test file deleted,四个归属套件保持绿色。
建议修复:跳过 before 与 after 均为空的事件(不携带 main 增量,唯一作用是翻转 baselinePresent)。约束:不得破坏 count-test-surface.mjs:384 —— 它是 MAIN 删除豁免轮次的唯一机制(由 merge-delete-freight 夹具钉住),因此该跳过只能在 before 同为空时触发。请补一个夹具(轮次 git rm F、mainMoves 编辑 F、合并按删除方向解决)断言以 test file deleted 拒绝;移除跳过守卫须变红。
— qwen3.8-max via Qwen Code /review (v0.23.0)
| git merge-file -p --ours "${p1}" "${base}" "${p2}" > "${out}" 2> /dev/null || true | ||
| [[ -s "${out}" ]] || cp "${p1}" "${out}" |
There was a problem hiding this comment.
[Critical] R21-4: [certifies-falsely] [new-surface] [[ -s "${out}" ]] conflates a legitimate EMPTY auto-merge result with a merge-file hard failure. When main empties a test file (keeps the file, deletes all content) and the round merges main, git merge-file -p --ours succeeds with rc=0 and 0-byte output; the -s test then copies the branch's pre-merge content over main's side, main's delta measures 0, and the round is rejected retryably with "net N assertion(s) removed" — a false charge for main's edit, contradicting the comment above's claim that only a hard failure leaves the output empty.
Witness:
Measured in the worktree shell:
git merge-file -p --ours with main's side truncated -> rc=0, out.ts size 0 (legitimate empty success)
[[ -s ]] false -> cp p1 fallback fires
End-to-end fixture (main empties F, round merges main unchanged):
PR: CAND3_STATUS=1, rejection '- pkg/a.test.ts — net 2 assertion(s) removed'
Constraint: NUL-byte (binary) input -> rc=255 with empty output, so the fallback must survive for nonzero exit
Fix flip (fall back to p1 only on nonzero exit): CAND3_STATUS=0, outcome=fixed, rejection '';
the conflict-resolution fixture stays green
Suggested fix — capture the exit status and fall back to the branch side only on failure, never on an empty success:
weaken_merge_rc=0
git merge-file -p --ours "${p1}" "${base}" "${p2}" > "${out}" 2> /dev/null || weaken_merge_rc=$?
if (( weaken_merge_rc != 0 )) && [[ ! -s "${out}" ]]; then
cp "${p1}" "${out}"
fiThe branch-side fallback must stay for nonzero exit (binary content exits 255 with empty stdout; a --ours conflict merge exits 0 with output, so an exit-status-based fallback is safe). Please pin it with a WEAKEN_FIXTURES entry where main empties F and the round merges main unchanged, asserting the gate accepts; restoring [[ -s ]] || cp must turn it red.
中文说明
[Critical] R21-4:[[ -s "${out}" ]] 把「合法的空自动合并结果」与「merge-file 硬失败」混为一谈。当 main 把一个测试文件清空(保留文件、删除全部内容)且轮次合并 main 时,git merge-file -p --ours 以 rc=0 输出 0 字节成功;-s 判定随后把分支侧合并前内容覆盖到 main 侧,main 增量测得 0,轮次被以「net N assertion(s) removed」可重试地拒绝——为 main 的编辑向轮次误计费,与上方注释「只有硬失败才会留下空输出」的说法矛盾。
证人证据:工作区实测 git merge-file -p --ours(main 侧截断为空)→ rc=0、输出 0 字节(合法的空成功);[[ -s ]] 为假 → cp p1 兜底触发。端到端夹具:原代码 status=1、拒绝「net 2 assertion(s) removed」;约束:NUL 字节(二进制)输入 → rc=255 且输出为空,因此非零退出必须保留兜底。修复翻转(仅非零退出兜底):status=0、outcome=fixed、无拒绝;冲突解决夹具保持绿色。
建议修复:捕获退出码,仅在失败时回退到分支侧(代码块见英文)。请补夹具(main 清空 F、轮次原样合并 main)断言门接受;还原 [[ -s ]] || cp 须变红。
— qwen3.8-max via Qwen Code /review (v0.23.0)
| cp .github/scripts/check-autofix-contracts.sh "${RUNNER_TEMP}/check-autofix-contracts.sh" | ||
| cp .github/scripts/resolve-owning-packages.sh "${RUNNER_TEMP}/resolve-owning-packages.sh" | ||
| cp .github/scripts/run-autofix-review-verification.sh "${RUNNER_TEMP}/run-autofix-review-verification.sh" | ||
| cp .github/scripts/count-test-surface.mjs "${RUNNER_TEMP}/count-test-surface.mjs" |
There was a problem hiding this comment.
[Critical] R21-5: [certifies-falsely] [new-surface] The staged counter is executed via node "${WEAKEN_COUNTER}" measure (run-autofix-review-verification.sh:1216) after branch code has already run on the host (the agent step; a no-sandbox relaunch path exists when QWEN_SANDBOX_IMAGE is empty), but no digest is recorded at staging and none is verified before execution — unlike the sibling staged copies protected by this same step (resanitize_sha256/verify_runner_sha256 recorded in GITHUB_OUTPUT at yml:3899-3903, checked with sha256sum -c at 5512/5772, af-111). The staging step's own comment states "RUNNER_TEMP is writable by the branch/agent code later steps run on this host", and the launch step's comment names the exact hazard: "a mid-run overwrite lets the branch define its own verdict". A swapped counter answering every manifest with zero deltas or baselinePresent:false launders every weakening — the probe shows it launders even whole-file deletions — and a corrupt counter degrades to fail-open UNAVAILABLE.
Witness:
End-to-end through the real gate with a planted launderer counter:
CAND5_HONEST_STATUS=1 ('round weakened pre-existing tests without recorded evidence')
CAND5_EVIL_STATUS=0, outcome=fixed, rejection ''
— same staged-counter path, same manifest, whole-file deletion laundered
grep of the workflow: no counter digest exists anywhere
(only resanitize_sha256 / verify_runner_sha256 / heartbeat digests)
Suggested fix — follow the af-111 sibling pattern: record weaken_counter_sha256=$(sha256sum "${RUNNER_TEMP}/count-test-surface.mjs" | cut -d' ' -f1) in the stage step's GITHUB_OUTPUT (guarded by -f, like the heartbeat), pass it into the gate step's env, and sha256sum -c it as close to the node invocation as possible — at use time, not just staging time; skip (not fail) when the digest is empty so base-absence still degrades to UNAVAILABLE. The digest must travel via GITHUB_OUTPUT (expression context, unreachable from a disk write after staging — the doctrine at yml:3893-3899), never via a file on disk. Please extend "stages the counter beside the gate runner, from the trusted base" to assert the stage step records the digest and the gate verifies it before node "${WEAKEN_COUNTER}" runs; removing either side must go red.
中文说明
[Critical] R21-5:暂存的计数器在分支代码已经于宿主机运行过之后(agent 步骤;QWEN_SANDBOX_IMAGE 为空时存在无沙箱重启路径)经 node "${WEAKEN_COUNTER}" measure 执行(run-autofix-review-verification.sh:1216),但暂存时未记录任何摘要、执行前也不校验——不同于同一步骤中受 af-111 保护的兄弟暂存副本(yml:3899-3903 记录摘要、5512/5772 以 sha256sum -c 校验)。暂存步骤自己的注释写明「RUNNER_TEMP 可被后续步骤运行的分支/代理代码写入」,启动步骤的注释点名同一危害:「运行中被覆写等于让分支自己定义判决」。被替换的计数器对每个清单回答零差值或 baselinePresent:false 即可洗白一切弱化——探针证明连整文件删除也能洗白——损坏的计数器则退化为放行式 UNAVAILABLE。
证人证据:真实门 + 植入的洗白计数器端到端执行:诚实计数器 status=1;植入计数器 status=0、outcome=fixed、无拒绝——同一暂存路径、同一清单,整文件删除被洗白。全 workflow grep:不存在任何计数器摘要。
建议修复:照 af-111 兄弟模式——暂存步骤以 -f 守卫把 weaken_counter_sha256 写入 GITHUB_OUTPUT,传入门步骤 env,在尽量贴近 node 调用处(使用时而非仅暂存时)sha256sum -c;摘要为空时跳过(不是失败),保持 base 缺失仍退化为 UNAVAILABLE。摘要必须经由 GITHUB_OUTPUT(表达式上下文,暂存后的磁盘写入不可达——yml:3893-3899 的原则),绝不落盘。请把「记录摘要 + 执行前校验」钉进现有暂存测试;移除任一侧须变红。
— qwen3.8-max via Qwen Code /review (v0.23.0)
| PUSH_PRE="${CHECK_STATE:-none}" | ||
| [[ "${PUSH_RACE_MERGED}" == 'true' ]] && PUSH_PRE='none' |
There was a problem hiding this comment.
[Critical] R21-6: [certifies-falsely] [new-surface] PUSH_PRE is blanked to none only for the race-salvage merge, not for a base-conflict merge: conflict rounds merge origin/main by design (SKILL.md: "If --conflict true, merge origin/<base>"), so the pushed head carries unclassified main content while the marker stamps pre=green from the pre-round classification. If merged main carries a failing check, the next prepare's charge conjuncts (LP_HEAD==CHECKED_OUT_HEAD && LP_PRE=='green' && CHECK_STATE=='red', yml:4785-4791) all hold — the branch ref never moved, so no conservative guard fires — and the round is publicly charged with a regression it did not author while the brake counts it as failure instead of progress. The comment above argues the carve-out for the race case ("the pushed head did not start from the head prepare classified: the premise is unknown, never green"); a base-conflict merge is the same epistemic shape and is unargued.
Witness:
Drove the script's actual stamp lines with CHECK_STATE=green, PUSH_RACE_MERGED=false, CONFLICT=true:
emitted: <!-- autofix-push round=4 head=abc123 pre=green key=w1 -->
with fix ([[ "${CONFLICT}" == 'true' ]] && PUSH_PRE='none' beside the existing carve-out):
pre=none with CONFLICT=true; pre=green preserved for CONFLICT=false
none is already in the scan grammar's vocabulary (pre=([a-z]+); the charge requires green)
Suggested fix:
[[ "${CONFLICT}" == 'true' ]] && PUSH_PRE='none'beside the existing carve-out — CONFLICT is already bound into the step env (steps.prepare.outputs.conflict) and read at script lines 619/691. The value must stay inside the scan grammar's vocabulary (pre=([a-z]+), green|red|pending|none, qwen-autofix.yml:4780). Please extend "stamps the push marker on the pushed report and nowhere else" (~line 26215) with the matching assertion for the conflict carve-out; removing the new guard must turn it red.
中文说明
[Critical] R21-6:PUSH_PRE 只在「竞态抢救合并」时被置为 none,base 冲突合并时不会:冲突轮次按设计合并 origin/main(SKILL.md:"If --conflict true, merge origin/<base>"),推送头因此携带未经分类的 main 内容,而标记仍按轮次前分类盖上 pre=green。若合并进来的 main 带有失败检查,下一轮 prepare 的计费合取式(LP_HEAD==CHECKED_OUT_HEAD && LP_PRE=='green' && CHECK_STATE=='red',yml:4785-4791)全部成立——分支引用从未移动,任何保守守卫都不触发——轮次被公开计费一笔并非它造成的回归,刹车把它记为失败而非进展。上方注释只为竞态情形论证了豁免(「推送头不是从 prepare 分类的那个头出发的:前提未知,绝不可能是 green」);base 冲突合并是同样的认知形状,却没有被论证。
证人证据:以 CHECK_STATE=green、PUSH_RACE_MERGED=false、CONFLICT=true 驱动脚本真实盖章行:输出 pre=green;加上修复(冲突时同样置 none)后 CONFLICT=true → pre=none、CONFLICT=false → pre=green 保持;none 本就在扫描语法词汇表内。
建议修复:在现有豁免旁补 [[ "${CONFLICT}" == 'true' ]] && PUSH_PRE='none'(CONFLICT 已经绑定进步骤 env,脚本 619/691 已在读)。取值须保持在扫描语法词汇表内。请把冲突豁免断言补进现有标记测试;移除新守卫须变红。
— qwen3.8-max via Qwen Code /review (v0.23.0)
| const require = createRequire(resolve(process.cwd(), 'package.json')); | ||
| const ts = require('typescript'); |
There was a problem hiding this comment.
[Critical] R21-7: [certifies-falsely] [new-surface] The gate's measuring instrument loads its parser (typescript) from the round's own checkout/node_modules — createRequire(resolve(process.cwd(), 'package.json')) with cwd the checkout (the gate step has no working-directory override; TRUSTED_PATH pins the node binary, not module resolution) — and the review-address job installs the branch's dependencies (npm ci, yml ~4029-4042) before the agent and the gate. The branch therefore selects the code that produces the gate's verdict: repointing typescript via a routine lockfile change (file:./vendor/ts, a git URL, or npm:<pkg> via overrides) whose createSourceFile returns empty trees makes every count() return ZERO, so tip − pre − events = 0 for every file and any weakening measures flat; since branch code has already run on the host, a plain rewrite of node_modules/typescript achieves the same without touching package.json. A digest of the staged counter script (the R21-5 concern) does not cover the dependency it imports. The in-code rationale ("resolves from the checkout ... the way every other check resolves its tooling") argues consistency, not measurement integrity — every other check is supposed to run branch code, while this one's verdict must not be branch-controllable; af-111 exists precisely because "a mid-run overwrite lets the branch define its own verdict".
Witness:
Same staged counter, same weakening manifest (pre: 2 assertions -> tip: 1):
CONTROL (cwd = scratch tree, real typescript): {"assertions":-1} — gate would reject
ATTACK (cwd = fixture checkout whose node_modules/typescript returns empty trees):
{"assertions":0} — weakening measures flat, gate passes
ATTACK-with-FIX (line 62 patched to createRequire(import.meta.url)):
{"assertions":-1} with the hostile cwd — the branch is cut out of resolution
Fix direction: cut the branch out of the parser — bundle the counter with its parser (esbuild typescript into the staged .mjs), or stage a pinned node_modules/typescript beside the counter and createRequire from the script's own location, or record/verify the resolved typescript module's digest in the same af-111 pattern as the staged scripts. The comment at count-test-surface.mjs:59-61 is mirrored by the fixture at scripts/tests/qwen-autofix-workflow.test.js:24637-24639 — the fix must keep the counter runnable from RUNNER_TEMP (outside any package root) or update both the comment and the fixture symlink together. Please pin it with a weaken-gate fixture planting a lying parser in the fixture checkout's node_modules: the gate must not produce a green result, and removing the fix's guard must turn that test red.
中文说明
[Critical] R21-7:门的测量仪器从轮次自己的 checkout/node_modules 加载解析器(typescript)——createRequire(resolve(process.cwd(), 'package.json')),cwd 即 checkout(门步骤没有 working-directory 覆盖;TRUSTED_PATH 钉的是 node 二进制,不管模块解析)——而 review-address 作业在 agent 与门之前安装分支依赖(npm ci,yml ~4029-4042)。因此分支可以选择产生门判决的代码:通过一次例行 lockfile 变更(file:./vendor/ts、git URL 或 overrides 里的 npm:<pkg>)把 typescript 换成 createSourceFile 返回空树的版本,每个 count() 都返回 ZERO,tip − pre − events 恒为 0,任何弱化都测得平坦;既然分支代码已在宿主机运行过,直接改写 node_modules/typescript 也能达到同样效果而不碰 package.json。暂存计数器脚本的摘要(R21-5 关注点)覆盖不到它所导入的依赖。代码内理由(「像其他检查一样从 checkout 解析工具」)论证的是一致性而非测量完整性——其他检查本来就该运行分支代码,而这个判决不能被分支控制;af-111 正是因为「运行中被覆写等于让分支自己定义判决」才存在。
证人证据:同一暂存计数器、同一弱化清单:对照(真实 typescript){"assertions":-1} 门应拒绝;攻击(植入空树解析器的 checkout){"assertions":0} 弱化测平、门放行;修复后(62 行改为 createRequire(import.meta.url))在恶意 cwd 下仍 {"assertions":-1}。
修复方向:把分支从解析器链路中切断——计数器与其解析器打包(esbuild 进暂存 .mjs),或在计数器旁暂存钉死的 node_modules/typescript 并从脚本自身位置 createRequire,或按 af-111 模式记录/校验解析出的 typescript 模块摘要。注意 :59-61 注释与测试夹具 :24637-24639 互为镜像,修复须保持计数器可从 RUNNER_TEMP 运行,或同时更新注释与夹具软链。请补「植入假解析器不得绿」的夹具,移除修复守卫须变红。
— qwen3.8-max via Qwen Code /review (v0.23.0)
| if [[ "${weaken_baseline}" != 'true' ]]; then | ||
| # Not the round's to weaken: the file is its own (pre-round absent and | ||
| # never landed by main) or main itself removed it. |
There was a problem hiding this comment.
[Critical] R21-8: [certifies-falsely] [new-surface] A rename decomposes under --no-renames into a charged deletion of the old path and a pre: null new path that this exemption blesses, so assertions removed IN THE SAME COMMIT AS A RENAME are never measured. The old path measures pre-present/tip-absent and is charged "test file deleted"; the new path measures pre: null with no main events, so baselinePresent=false and this exemption fires; the agent acks the old path with a rename reason (≥40 chars) and the gate accepts — while the shrinkage inside the destination produces no machine signal anywhere, and the maintainer-facing advisory says a rename happened while the removed assertions ride through unmeasured. The exemption's comment names the documented case ("the file is its own"), but a rename destination existed pre-round under the source name, so applying it here is a misclassification, not the documented new-file case.
Witness:
Full gate via the suite's own runGate harness at the reviewed commit:
UNACKED: rejection lists only 'pkg/a.test.ts — test file deleted'
ACKED (rename reason >=40 chars): status=0, advisory shows the deletion + the agent's reason
— although the round deleted one of two assertions inside pkg/b.test.ts
WITH --find-renames pair fix:
UNACKED rejection adds 'pkg/b.test.ts — net 1 assertion(s) removed'
ACKED round rejected (status 1) naming pkg/b.test.ts
existing tests (honest edits / deleted pre-existing / tip-whichever-sequence) all stay green
— pure additions carry no R entry, so the truly-new-file exemption is intact
Suggested fix — detect rename pairs across the round once (git diff -z --name-status --find-renames "origin/${BRANCH}" "${BRANCH}" -- "${WEAKEN_PATHSPEC[@]}") and, for each R old new pair whose destination is a measured candidate, carry the baseline across the rename: in weaken_measure, substitute the destination's pre with origin/${BRANCH}'s blob of the source path so the tip is judged against the pre-rename surface. The exemption must keep blessing genuinely new test files ("the file is its own (pre-round absent and never landed by main)") — the fix must not charge a file the round truly created. Please pin it with a weaken fixture whose round does git mv pkg/a.test.ts pkg/b.test.ts plus an assertion-deleting rewrite of pkg/b.test.ts, asserted through rejectsWeakening with a signal naming the destination path; it goes red today.
中文说明
[Critical] R21-8:在 --no-renames 下,一次改名分解为「旧路径被计费为删除」+「新路径 pre: null 被此豁免祝福」,于是与改名同一提交中删除的断言完全不被测量:旧路径测得「轮前存在、tip 缺失」→ 计费 test file deleted;新路径 pre: null、无 main 事件 → baselinePresent=false → 此豁免触发;agent 用 ≥40 字的改名理由 ack 旧路径,门接受——而目标文件内部的断言缩水不产生任何机器信号,面向维护者的 advisory 只说发生了改名,被删断言无人测量地放行。豁免注释点名的是文档情形(「文件是本轮自己的」),但改名目标在轮次前就以源路径名存在,此处套用是误分类而非文档中的新文件情形。
证人证据:用套件自带 runGate 真实门执行:未 ack 时拒绝只列旧路径删除;以改名理由 ack 后 status=0(尽管轮次删掉了 pkg/b.test.ts 两条断言之一);加上 --find-renames 配对修复后:未 ack 拒绝新增目标路径的「net 1 assertion(s) removed」,ack 轮也被拒绝;既有测试保持绿色(纯新增不带 R 条目,真正的新文件豁免不受影响)。
建议修复:整轮一次性检测改名对(git diff -z --name-status --find-renames ...),对每个目标在测量候选中的 R old new 对,把基线带过改名——在 weaken_measure 中用源路径在 origin/${BRANCH} 上的 blob 替代目标的 pre。豁免必须继续祝福真正的新文件,不得计费轮次真正创建的文件。请补夹具(git mv + 目标文件内删断言)断言以点名目标路径的信号拒绝;今天它是红的。
— qwen3.8-max via Qwen Code /review (v0.23.0)
| done < <(git diff --name-only -z --no-renames --diff-filter=D "origin/${BRANCH}" "${BRANCH}" \ | ||
| -- "${WEAKEN_PATHSPEC[@]}" 2> /dev/null) |
There was a problem hiding this comment.
[Critical] R21-9: [certifies-falsely] [new-surface] The UNAVAILABLE arm's deletion enumeration filters on --diff-filter=D only, which misses typechange (T): replacing an existing test file with a symlink removes its entire declared surface without producing a deletion entry, so the arm fails open for exactly the class its contract says it never fails open for ("Fails OPEN on the measured signals ... and never on a whole-file deletion, which the pre-round->tip pair proves without the walk" — the same pair proves a typechange). The measured branch catches this shape (per-commit enumeration has no filter; the tip blob parses as the symlink's target text and yields a negative delta), so the hole exists only in this degraded arm — which the suite itself exercises via noCounter: true and any walk/counter failure reaches in production.
Witness:
Git-level probe in a throwaway repo:
T pkg/a.test.ts under --name-status
--diff-filter=D -> (empty)
--diff-filter=DT -> pkg/a.test.ts
End-to-end gate probe (existing symlink fixture through noCounter:true):
UNMODIFIED: status=0, rejection '' — round accepted with pkg/a.test.ts now a symlink
stdout: 'test-weakening measurement UNAVAILABLE this round' (expected +1, received 0)
WITH --diff-filter=DT: status=1, rejection contains 'test file deleted'
the path-based exemption checks (git cat-file -e "${PR_BASE}:${f}" / origin/main:"${f}")
work unchanged on the T entry
Suggested fix — widen the degraded enumeration:
done < <(git diff --name-only -z --no-renames --diff-filter=DT "origin/${BRANCH}" "${BRANCH}" \
-- "${WEAKEN_PATHSPEC[@]}" 2> /dev/null)The arm must stay deletion-judging only (header contract at run-autofix-review-verification.sh:1093-1096) — widening the filter must not start measuring content signals without the walk. Please extend "judges deletions even when the measurement is unavailable" (~line 25506) with a fixture whose round swaps a pathspec file for a symlink under noCounter: true, asserting status 1 with "test file deleted"; today it passes green at status 0.
中文说明
[Critical] R21-9:UNAVAILABLE 分支的删除枚举只按 --diff-filter=D 过滤,漏掉类型变更(T):把既有测试文件替换为符号链接会移除其全部声明表面却不产生删除条目,于是该分支恰好在契约声称「绝不放行」的类别上放行(「测量信号放行……整文件删除绝不放行——轮前→tip 对无需走查即可证明」,而同一对也能证明类型变更)。测量分支能抓住该形态(逐提交枚举无过滤;tip blob 按符号链接目标文本解析出负差值),所以漏洞只存在于这个退化分支——套件自己用 noCounter: true 演练它,生产中任何走查/计数器失败都会到达。
证人证据:临时仓库 git 探针:--name-status 报 T pkg/a.test.ts;--diff-filter=D 为空;--diff-filter=DT 列出该路径。端到端门探针(现有 symlink 夹具走 noCounter:true):原代码 status=0、无拒绝——文件已变符号链接仍被接受;改为 --diff-filter=DT 后 status=1、拒绝含 test file deleted;基于路径的豁免检查对 T 条目不变可用。
建议修复:把退化枚举放宽为 --diff-filter=DT(代码块见英文)。该分支必须仅做删除判定(头部契约 :1093-1096),放宽过滤不得开始测量内容信号。请把「无测量时也判删除」测试扩展一个符号链接替换夹具(noCounter:true),断言 status 1 且含 test file deleted;今天它以 status 0 绿着。
— qwen3.8-max via Qwen Code /review (v0.23.0)
| # snapshots excluded. Non-JS shapes measure a zero surface and are judged | ||
| # by the deletion arm alone. A test surface only a runner can enumerate -- | ||
| # a Rust `#[cfg(test)]` module inside a production file, a suite registered | ||
| # under a condition -- is outside this gate by design. |
There was a problem hiding this comment.
[Critical] R18-2: [certifies-falsely] [new-surface] Still stands (carried id from round 20): the name-based WEAKEN_PATHSPEC (*.test.*, *.spec.*, test_*.py, tests/*.rs, *_test.rs, *_tests.rs, line 1097) still cannot see a Rust module whose tests are an inline #[cfg(test)] mod tests block — 207 Rust src/*.rs files carrying inline #[test] functions were measured unenumerated at round 20, and no name-based pathspec can select them. A round that deletes or guts such a module is accepted with no ack and no advisory, while the gate's CONTRACT promises every pre-existing test file whose declared surface shrinks must be named in test-weakening.json. This header line documents the boundary ("outside this gate by design"), which makes the limitation deliberate — but a documented limitation is not a closure of the blocker's structural demand (ask the runner which files it collected, and apply that set to both the rejecting pathspec and the advisory's), and the demonstrated harm still fires at the reviewed commit. The related non-JS inline-weakening silence for files that ARE in the pathspec is recorded separately as a low-confidence new finding this round.
Witness:
Round-20 measurement (git ls-files driven with the pathspec as authority):
paths matched by WEAKEN_PATHSPEC: 2507
Rust src/*.rs modules carrying inline #[test], unenumerated: 207
Code age at round 21: the pathspec at line 1097 still carries only the six test-named glob arms;
the declared-limit comment anchored here states the same boundary. No runner-derived enumeration was added.
Fix direction: derive the measured set from the runner (the vitest/cargo collection output) and apply it to both WEAKEN_PATHSPEC and the advisory's TEST_PATHSPEC — or, at minimum, add a cargo-test-derived enumeration for src/*.rs modules carrying #[cfg(test)] blocks. The fix must respect the header invariant at run-autofix-review-verification.sh:1140-1143 — "The rejecting gate must not see strictly less of the repo's test surface than the advisory's TEST_PATHSPEC above" — so a runner-derived set must be applied to both arrays. Please pin it: the committed "rejects deleting a Rust test file named *_tests.rs" and "rejects deleting a non-JS test file the JS pathspec missed" pins must stay status 1, and a new fixture deleting a src/*.rs file whose only test content is an inline #[cfg(test)] mod tests must reach status 1 — today the pathspec cannot select it at all.
中文说明
[Critical] R18-2(沿用第 20 轮编号):仍然存在——按文件名选取的 WEAKEN_PATHSPEC(行 1097 的六个测试命名分支)依然看不见「测试内容是内联 #[cfg(test)] mod tests 块」的 Rust 模块:第 20 轮实测有 207 个携带内联 #[test] 的 Rust src/*.rs 文件未被枚举,且任何按名的 pathspec 都无法选中它们。一轮修复删除或掏空这样的模块会在既无 ack 也无 advisory 的情况下被接受,而门的 CONTRACT 承诺任何声明表面缩水的既有测试文件都必须写进 test-weakening.json。锚定的这行头部注释把该边界写成了设计(「本门之外,设计上如此」),使限制成为有意为之——但「写入注释」不等于满足本阻塞项的结构性诉求(询问运行器实际收集了哪些文件,并把该集合同时应用到拒绝性 pathspec 与 advisory 的 TEST_PATHSPEC),且已验证的危害在被审提交上仍然成立。与之相关、针对「在 pathspec 内」的非 JS 文件内联弱化静默问题,本轮已作为低置信新发现另行记录。
证人证据:第 20 轮以 git ls-files + 完整 pathspec 为权威实测:匹配 2507 条路径;未枚举的内联 #[test] Rust src/*.rs 模块 207 个。第 21 轮代码年龄测量:行 1097 的 pathspec 仍只有六个测试命名分支,锚定注释仍声明同一边界,未新增任何运行器推导的枚举。
修复方向:以运行器推导的集合(vitest/cargo 的收集输出)为准,同时应用到 WEAKEN_PATHSPEC 与 advisory 的 TEST_PATHSPEC;至少为携带 #[cfg(test)] 块的 src/*.rs 模块加入 cargo 推导的枚举。修复须尊重 :1140-1143 的抬头不变量(拒绝性门看到的测试面不得严格少于 advisory 的 TEST_PATHSPEC)。请补钉子:既有的两个删除拒绝测试保持 status 1,新增「删除唯一测试内容为内联 #[cfg(test)] mod tests 的 src/*.rs 文件」夹具须达 status 1——今天 pathspec 根本选不中它。
— qwen3.8-max via Qwen Code /review (v0.23.0)
|
🤖 AutoFix ran out of time before finishing (timeout (7200000ms)) (attempt 16/100) — it will retry on the next scan. What I found before stopping: See the Qwen Autofix agent step logs for model/tool output. 中文说明🤖 AutoFix 在完成前耗尽了时间(timeout (7200000ms))(第 16/100 次尝试)—— 将在下次扫描时重试。 Run log: https://github.com/QwenLM/qwen-code/actions/runs/33834880283 🧠 Handled by Qwen Code · model/模型 |
|
🔀 Base updated: red check(s) [Test (ubuntu-latest, Node 22.x)] pass on current main — merged current main via update-branch; CI will re-run. 中文说明🔀 已更新 base:红色检查 [Test (ubuntu-latest, Node 22.x)] 在当前 main 上通过 —— 已通过 update-branch 合入当前 main,CI 将重新运行。 |
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
Partially reviewed — gaps disclosed.
Not reviewed: the executable-script lint — .github/scripts/autofix-push-and-report.sh: shellcheck is not installed.
Not reviewed: the executable-script lint — .github/scripts/run-autofix-review-verification.sh: shellcheck is not installed.
Not explored to full depth (tool budget reached): chunk 6: dynamic execution of the new af-155 and gate A/B tests — the review worktree has no node_modules (vitest/typescript uninstalled), so I verified by tracing each …; "agent 1c": none — but I did not exhaustively re-derive every weaken_auto_blob conflict-resolution permutation beyond the fixture-covered shapes (merge-freight, merge-del….
Not reviewed: reverse audit — stopped before round 5 by the review time budget.
Not linted (tool limitation, not a blocker): .github/workflows/qwen-autofix.yml — actionlint embedded-shell source mapping is not yet supported.
Deferred under the convergence posture (round 22, not a blocker) — recorded, not requested in this round:
scripts/tests/qwen-autofix-workflow.test.js:25668 — [review] Unpinned counter branches ship green under mutation (fails/failing, xtest, truthy-literal numeric/string forms, computed propertyName, template titleOf).github/scripts/autofix-push-and-report.sh:653 — [review] PUSH_PRE fail-safe default ${CHECK_STATE:-none} unpinned; :-green mutation ships green.github/scripts/run-autofix-review-verification.sh:1335 — [review] Ack channel cannot express invalid-UTF-8 paths; retryable rejection loops despite recorded evidencescripts/tests/qwen-autofix-workflow.test.js:25289 — [review] bash-3.2 pin regex misses &>>, ;&, ${var,}/${var^}, coproc, local -n, wait -n, globstar familiesscripts/tests/qwen-autofix-workflow.test.js:25293 — [review] Accepted-round tab-name test escapes the hasBashMapfile host gate, unlisted in the meta-pin, asserts no verdictscripts/tests/qwen-autofix-workflow.test.js:26235 — [review] ROLLUP_HEAD derivation ordering relative to the regression-accounting block unpinned; a reorder kills the charge silently
Convergence: round 22 posted 13 inline comment(s), 3 of them reported for the first time; the previous round posted 10 (9 new). Findings keep coming back to the same files: .github/scripts/run-autofix-review-verification.sh (findings in rounds 18, 21; 1 more now); .github/scripts/autofix-push-and-report.sh (findings in round 21; 1 more now); .github/workflows/qwen-autofix.yml (findings in round 21; 1 more now). A cluster that keeps producing siblings usually means the fixes are treating instances of a shared root cause — triaging that cause before the next round, or splitting an independent cluster into its own pull request, tends to end the loop faster than fixing them one at a time. (Observation only — nothing was withheld from this review because of this observation.)
Mechanism health: this round did not close cleanly, so it withholds the incremental anchor — and the round it recovered had no anchor this round could use either — none at all, one with no certifier, one certified by an identity other than the one this round runs under, or one this round's fetch refused or resolved to the head — so the next review re-reads the whole diff unless recovery grafts an earlier own anchor that the round running it can use onto the complete work list this round leaves behind, and keeps doing so until a round's marker carries an anchor again or a graft lands that the round running it can use. (Stated, not acted on — this changes nothing about what the round posts.)
中文说明
仅完成部分审查,审查缺口已披露。
未审查(原文为英文):the executable-script lint — .github/scripts/autofix-push-and-report.sh: shellcheck is not installed.
未审查(原文为英文):the executable-script lint — .github/scripts/run-autofix-review-verification.sh: shellcheck is not installed.
未探索到全部深度(达到工具调用预算):chunk 6:dynamic execution of the new af-155 and gate A/B tests — the review worktree has no node_modules (vitest/typescript uninstalled), so I verified by tracing each …;"agent 1c":none — but I did not exhaustively re-derive every weaken_auto_blob conflict-resolution permutation beyond the fixture-covered shapes (merge-freight, merge-del…。
未审查:反向审计——评审时间预算不足,未能开始第 5 轮。
未检查(工具限制,非阻断):.github/workflows/qwen-autofix.yml——actionlint 对 workflow 内嵌 shell 的源映射尚未支持。
收敛姿态下延后(第 22 轮,非阻断)——已记录,本轮不要求修改:共 6 条(原文未翻译,列表见上方英文部分)。
收敛情况:第 22 轮发布了 13 条行内评论,其中 3 条是首次提出;上一轮发布了 10 条(其中 9 条首次提出)。发现反复回到同一批文件:.github/scripts/run-autofix-review-verification.sh(第 18、21 轮已出过发现,本轮又有 1 条);.github/scripts/autofix-push-and-report.sh(第 21 轮已出过发现,本轮又有 1 条);.github/workflows/qwen-autofix.yml(第 21 轮已出过发现,本轮又有 1 条)。一个不断再生兄弟发现的簇,通常意味着逐条修复只在处理同一根因的实例——先定位并处理该根因,或把独立的簇拆成单独的 PR,通常比逐条修复更快结束循环。(仅为观察——本轮评审未因此扣留任何内容。)
机制健康:本轮未能干净收尾,因而扣留了增量锚点,而它恢复到的那一轮也没有留下本轮可用的锚点——要么完全没有、要么没有认证者、要么由本轮运行身份之外的身份认证、要么被本轮的获取拒绝或解析为头提交——因此下一次评审将重读整个 diff,除非恢复流程把本轮能使用的更早自有锚点嫁接到本轮留下的完整工作清单上;并会一直如此,直到某一轮的标记重新带上锚点,或落地的嫁接能被运行该轮的评审使用。(仅陈述,不据此行动——这不改变本轮发布的任何内容。)
— qwen3.8-max via Qwen Code /review (v0.23.0)
| for (const k of before.enabledTitles) { | ||
| if (!afterEnabled.has(k)) mainDisabled.add(k); | ||
| } |
There was a problem hiding this comment.
[Critical] R21-1: [certifies-falsely] [new-surface] Still stands (carried from round 21): class-level finding — measure() attributes by title-keyed Set membership and position-free net deltas with no per-registration identity, multiplicity, or event pairing. The seven round-21 entrances stand unchanged (the PR has pushed no code since): (1) mainDisabled is add-only across events — the stale shield lets a round disabling a re-enabled title escape with newlyDisabled:[] plus one balancing test; (2) enabled counts describe/suite wrappers like tests; (3) position-free kind:title collisions; (4) title-granular, not multiplicity-aware, shielding; (5) merge after.enabledTitles leaks the round's own registrations into the baseline; (6) a negative event delta is credited unconditionally; (7) renamed-then-disabled escapes.
This round demonstrates one further entrance at this commit: the enabled arithmetic is kind-blind (test and describe registrations are fungible) and newlyDisabled only sees keys present in the tip's disabled list, so converting test('x', body) into describe('x', same body) deletes the runtime test case with every signal at zero — vitest registers zero test cases for the describe body, and under the gate's own --passWithNoTests flag the empty suite passes, so the weakening is end-to-end silent.
Witness:
Round-22 probe at this commit (code byte-identical to round 21):
measure(test('x') -> describe('x')) => {"assertions":0,"guards":0,"enabled":0,"newlyDisabled":[]}
control outright deletion => {"assertions":-1,"enabled":-1}
vitest: converted.test.js (0 test) passes with the gate's --passWithNoTests flag
Round-21 witness, entrance (1): stale-shield escape => newlyDisabled:[]
with mainDisabled.delete(k) fix => newlyDisabled:["test:a"]
Fix direction: replace the title-set algebra with per-registration, multiplicity-aware attribution — mainDisabled as a Map of key→count built as the multiset difference of before/after enabledTitles per event (re-enabled titles consume shield counts); baselineEnabled extended only with titles main itself introduced; newlyDisabled as the multiset difference of tip-disabled against baseline-disabled and the shield counts; title-similarity pairing for renamed-then-disabled; and make enabled kind-aware so a test→describe conversion nets −1 enabled test.
The fix must keep newlyDisabled an array whose length equals the number of registrations charged — run-autofix-review-verification.sh:1273 reads (.newlyDisabled | length) to build the charge text — and the header contract at count-test-surface.mjs:46-53 ("main's contribution neither charges nor shields") is the acceptance bar. Please pin each arm, including a measure case asserting test('x') → describe('x') reports a removal; removing any arm of the fix must redden exactly its own scenario while the existing single-event scenarios stay green.
中文说明
仍然存在(沿用第 21 轮编号):类级问题——measure() 按「标题键集合成员关系」与「无位置净差值」归属,没有按注册项的身份、重数与事件配对。第 21 轮的七个入口原样成立(PR 此后未推送任何代码):(1) mainDisabled 跨事件只增不减——陈旧豁免让「重新启用后再禁用」的轮次以 newlyDisabled:[] 加一个平衡测试逃逸;(2) enabled 把 describe/suite 包装器当测试计数;(3) 无位置的 kind:title 键碰撞;(4) 豁免按标题粒度而非重数;(5) merge 事件的 after.enabledTitles 把轮次自己的注册泄漏进基线;(6) 事件负差值无条件计信;(7) 改名后禁用可逃逸。
本轮在本提交上验证了一个新入口:enabled 运算不区分 kind(test 与 describe 可互换),且 newlyDisabled 只看 tip 禁用列表中出现的键,因此把 test('x', body) 改写成 describe('x', same body) 会在所有信号为零的情况下删除运行时测试——vitest 对 describe 体不注册任何测试用例,而门自己的 --passWithNoTests 标志让空套件通过,弱化全程静默。
证人证据:见英文版探针输出(本提交执行;代码与第 21 轮逐字节一致)。
修复方向:以按注册项、重数感知的归属取代标题集合代数(mainDisabled 改为键→计数 Map,按事件做多重集差,被重新启用的标题消耗豁免计数;baselineEnabled 只并入 main 自己引入的标题;newlyDisabled 按多重集差计算;引入标题相似度配对;并让 enabled 区分 kind,使 test→describe 转换净 −1)。修复须保持 newlyDisabled 数组长度等于被计费注册数(run-autofix-review-verification.sh:1273 读取其长度生成计费文本),验收标准是 count-test-surface.mjs:46-53 的头部契约。请为每个入口补钉子,包括断言 test→describe 报告删除的 measure 用例;移除修复任一部分须恰好使对应场景变红,既有单事件场景保持绿色。
— qwen3.8-max via Qwen Code /review (v0.23.0)
| registrations.push({ | ||
| kind: ROOTS[chain.root] ?? XROOTS[chain.root], |
There was a problem hiding this comment.
[Critical] R21-2: [certifies-falsely] [new-surface] Still stands (carried from round 21): class-level finding — count() decides registration, disabling and guard attribution by chain-root identifier name and syntactic shape, with no binding awareness and no control-flow modeling. The five round-21 entrances (plus three low-confidence siblings) stand unchanged: parenthesized/unary wrappers never peeled by truthyLiteral/falsyLiteral/optionsDisable; isBodySkip ignoring enclosing control flow; positional containment across the nested-function boundary; fn:null callbacks and the phantom {kind:'test',title:''} registration; phantom title-key collisions. The surface cannot be completed entrance by entrance — every spelling fix reopens on the next JS construct.
This round demonstrates SEVEN further entrances, each probed at this commit:
(a) optionsDisable decodes only the literal true keyword, but vitest truthy-checks options — it('a', { skip: 'flaky under load' }, fn) silently disables while the counter measures it enabled;
(b) a disabled suite (describe.skip) never propagates disabling to nested registrations — a no-op .skip inside an already-skipped suite false-charges, and describe→describe.skip under-charges;
(c) the guard walk aborts (lastAssertion < 0) when a test's assertions live in a module-level helper — an early-return weakening there is invisible;
(d) the guard signal counts only bare return; — return undefined; / return void 0; / return null; bypass by spelling;
(e) optionsDisable recognises only skip/todo while vitest's TestOptions.fails inverts the verdict — { fails: true } pins the OPPOSITE behaviour with zero signal, while the .fails chain member IS classified;
(f) a body-level ctx.skip() in a flat file's top-level beforeEach records the inert phantom entry while vitest skips EVERY test in the file;
(g) assertion detection accepts only ExpressionStatement/ReturnStatement/arrow-body positions and chainOf peels only NonNull/Paren mid-chain — assertions executed from ternary/logical/comma/prefix-unary/yield positions, or cut off by a mid-chain as/satisfies cast, count zero; deleting them measures all-zero.
Witness:
Probes at this commit (unmodified counter vs pinned vitest 3.2.7):
(a) {skip:'flaky under load'} counts enabled:1 == PRE; vitest: 1 passed | 1 skipped, exit 0
(d) insert 'return;' -> guards=1, signal fires; insert 'return undefined;' -> guards=0, EMPTY
(e) measure(pre=it('a',fn), tip=it('a',{fails:true},fn)) all-zero;
vitest passes the throwing body under {fails:true}
(f) flat file + beforeEach(ctx=>ctx.skip()): measure all-zero; vitest: Tests 2 skipped (2)
(g) seven conditional-position assertions -> assertions:0; their deletion measures all-zero
The class fix round 21 asked for stands: replace name/shape attribution with runner-grounded semantics (the runner's own collected-test enumeration, or per-construct modeling of vitest's truthiness/focus/fails/options and nesting). Per-entrance spelling fixes will reopen on the next construct — rounds 12 to 22 of this PR's own history are the evidence. Please pin each demonstrated entrance; removing any arm of the fix must redden exactly its own row. Condition-valued guards (skipIf(cond), skip(cond, reason)) must stay uncharged per the NOT MEASURED contract (run-autofix-review-verification.sh:1085-1090), and assertion-carrying returns must keep counting the assertion and not a guard.
中文说明
仍然存在(沿用第 21 轮编号):类级问题——count() 仅凭链根标识符名与语法形状判定注册、禁用与守卫归属,不做绑定分析、不建模控制流。第 21 轮的五个入口(加三个低置信兄弟形态)原样成立:字面量判定不剥括号/一元包装;isBodySkip 不看外层控制流;跨嵌套函数边界的纯位置包含;fn:null 回调与幻影注册;幻影标题键碰撞。该检测面无法逐入口补完——每修一种写法都会被下一种 JS 构造重新打开。
本轮在本提交上验证了七个新入口:(a) optionsDisable 只解码字面量 true,而 vitest 对 options 做真假值判定——{ skip: '理由字符串' } 静默禁用却被测为启用;(b) 禁用的套件不向嵌套注册传播——已跳过套件里的无操作 .skip 造成误记账,describe→describe.skip 记账不足;(c) 断言位于模块级辅助函数时守卫遍历中止,提前 return 弱化不可见;(d) 守卫信号只数裸 return;——return undefined;/void 0/null 按拼写绕过;(e) optionsDisable 只认 skip/todo,而 vitest 的 TestOptions.fails 反转判决——{ fails: true } 零信号钉住相反行为(链成员 .fails 却被分类);(f) 平铺文件顶层 beforeEach 里的 ctx.skip() 只产生惰性的幻影条目,而 vitest 跳过全文件测试;(g) 断言检测只接受 ExpressionStatement/ReturnStatement/箭头体位置,链中 as/satisfies 截断根——三元/逻辑/逗号/前缀一元/yield 位置的断言计零,删除它们测得全零。
证人证据:见英文版探针输出(未改动计数器与钉住的 vitest 3.2.7 对照,本提交执行)。
第 21 轮请求的类级修复仍然成立:以运行器背书的语义取代按名/形状归属(运行器自己的收集清单,或对 vitest 真假值/only/fails/options 与嵌套的逐构造建模)。逐入口修补会被下一种构造重新打开——本 PR 第 12–22 轮的历史就是证据。请为每个已验证入口补钉子;移除修复任一部分须恰好使自己的行变红。条件式守卫(skipIf(cond)、skip(cond, reason))按 NOT MEASURED 契约保持不计费;携带断言的 return 须继续计断言而不计守卫。
— qwen3.8-max via Qwen Code /review (v0.23.0)
| # The branch holds no blob: main added the file and it lands; or the | ||
| # branch deleted it earlier and main's edit is a modify/delete conflict | ||
| # resolved for the branch's deletion. | ||
| [[ -z "${base}" ]] && printf '%s\n' "${p2}" |
There was a problem hiding this comment.
[Critical] R21-3: [certifies-falsely] [new-surface] Still stands (carried from round 21): a round's own deletion of a pre-existing test file is silently exempted when a main-derived merge follows the deletion and main's side still holds the file. At the merge commit the first-parent diff is quiet (absent vs absent) but the second-parent diff differs, so an event is recorded; weaken_auto_blob hits this p1-empty branch with a non-empty base and returns nothing; the event becomes {before: null, after: null}; baselinePresent flips false; the shell takes the "not the round's to weaken" branch and accepts — although the file was held by the pre-round baseline and is absent at the tip, against this section's stated "never on a whole-file deletion" contract. The code is unchanged since round 21.
Witness:
Round-21 end-to-end through the real runGate harness:
PR: CAND2_STATUS=0, outcome=fixed, rejection '' (deletion exempted)
fix ([[ -n "${before}" || -n "${after}" ]] || continue before events+=):
CAND2_STATUS=1, rejection '- pkg/a.test.ts — test file deleted'
four attribution suites stay green under the fix
Suggested fix — skip recording events where both before and after are empty; they carry no main delta and their only effect is the baselinePresent flip:
[[ -n "${before}" || -n "${after}" ]] || continueThe fix must not break count-test-surface.mjs:384 (baselinePresent = ev.after !== null), the only mechanism by which a MAIN deletion exempts a round (pinned by the merge-delete-freight fixture) — the skip may fire only when before is also empty. Please pin it with a WEAKEN_FIXTURES entry (round git rms F, mainMoves edits F, merge resolved for the deletion) asserting rejection with signal "test file deleted"; removing the skip guard must turn it red.
中文说明
仍然存在(沿用第 21 轮编号):当轮次自己删除既有测试文件、随后发生 main 派生合并且 main 侧仍持有该文件时,轮次自己的删除被静默豁免:合并提交上第一父 diff 静默(缺失对缺失)而第二父 diff 有差异,于是记录了事件;weaken_auto_blob 走到 p1 为空、base 非空的分支并返回空;事件成为 {before: null, after: null};baselinePresent 翻为 false;shell 走「非本轮可弱化」分支直接接受——尽管文件在轮前基线存在、在 tip 缺失,违反本节「整文件删除绝不放行」的契约。代码自第 21 轮未变。
证人证据:见英文版 runGate 真实门端到端输出(原代码放行;修复后以 'test file deleted' 拒绝,四个归属套件保持绿色)。
建议修复:跳过 before 与 after 均为空的事件(代码块见英文)。约束:不得破坏 count-test-surface.mjs:384——它是 MAIN 删除豁免轮次的唯一机制(由 merge-delete-freight 夹具钉住),跳过只能在 before 同为空时触发。请补夹具(轮次 git rm F、mainMoves 编辑 F、合并按删除方向解决)断言以 'test file deleted' 拒绝;移除跳过守卫须变红。
— qwen3.8-max via Qwen Code /review (v0.23.0)
| # Conflicts resolve for the branch (--ours). Only a hard failure (binary | ||
| # content) leaves the output empty, and then the branch's side stands. | ||
| git merge-file -p --ours "${p1}" "${base}" "${p2}" > "${out}" 2> /dev/null || true | ||
| [[ -s "${out}" ]] || cp "${p1}" "${out}" |
There was a problem hiding this comment.
[Critical] R21-4: [certifies-falsely] [new-surface] Still stands (carried from round 21): [[ -s "${out}" ]] conflates a legitimate EMPTY auto-merge result with a merge-file hard failure. When main empties a test file (keeps the file, deletes all content) and the round merges main, git merge-file -p --ours succeeds with rc=0 and 0-byte output; the -s test then copies the branch's pre-merge content over main's side, main's delta measures 0, and the round is rejected retryably with "net N assertion(s) removed" — a false charge for main's edit, contradicting the comment above's claim that only a hard failure leaves the output empty. Re-measured at this commit by round 22.
Witness:
Round-22 measurement at this commit:
git merge-file -p --ours with main's side truncated -> rc=0, out size 0 (legitimate empty success)
[[ -s ]] false -> cp p1 fallback fires
Round-21 end-to-end fixture (main empties F, round merges main unchanged):
PR: CAND3_STATUS=1, rejection '- pkg/a.test.ts — net 2 assertion(s) removed'
Fix flip (fall back to p1 only on nonzero exit): CAND3_STATUS=0, outcome=fixed
Constraint: NUL-byte (binary) input -> rc=255 with empty output, so the fallback must survive for nonzero exit
Suggested fix — capture the exit status and fall back to the branch side only on failure, never on an empty success:
weaken_merge_rc=0
git merge-file -p --ours "${p1}" "${base}" "${p2}" > "${out}" 2> /dev/null || weaken_merge_rc=$?
if (( weaken_merge_rc != 0 )) && [[ ! -s "${out}" ]]; then
cp "${p1}" "${out}"
fiThe branch-side fallback must stay for nonzero exit (binary content exits 255 with empty stdout; a --ours conflict merge exits 0 with output, so an exit-status-based fallback is safe). Please pin it with a WEAKEN_FIXTURES entry where main empties F and the round merges main unchanged, asserting the gate accepts; restoring [[ -s ]] || cp must turn it red.
中文说明
仍然存在(沿用第 21 轮编号):[[ -s "${out}" ]] 把「合法的空自动合并结果」与「merge-file 硬失败」混为一谈。当 main 清空测试文件(保留文件、删除全部内容)且轮次合并 main 时,git merge-file -p --ours 以 rc=0 输出 0 字节成功;-s 判定随后把分支侧内容覆盖到 main 侧,main 增量测得 0,轮次被以「net N assertion(s) removed」可重试地拒绝——为 main 的编辑向轮次误计费,与上方注释「只有硬失败才留下空输出」矛盾。第 22 轮在本提交上复测确认。
证人证据:见英文版(本提交实测 + 第 21 轮端到端夹具:原代码误拒;仅非零退出兜底后接受;二进制输入 rc=255 约束)。
建议修复(代码块见英文):捕获退出码,仅在失败时回退分支侧。请补夹具(main 清空 F、轮次原样合并 main)断言门接受;还原 [[ -s ]] || cp 须变红。
— qwen3.8-max via Qwen Code /review (v0.23.0)
| cp .github/scripts/check-autofix-contracts.sh "${RUNNER_TEMP}/check-autofix-contracts.sh" | ||
| cp .github/scripts/resolve-owning-packages.sh "${RUNNER_TEMP}/resolve-owning-packages.sh" | ||
| cp .github/scripts/run-autofix-review-verification.sh "${RUNNER_TEMP}/run-autofix-review-verification.sh" | ||
| cp .github/scripts/count-test-surface.mjs "${RUNNER_TEMP}/count-test-surface.mjs" |
There was a problem hiding this comment.
[Critical] R21-5: [certifies-falsely] [new-surface] Still stands (carried from round 21): the staged counter — the measurement authority of the new test-weakening gate — is executed via node "${WEAKEN_COUNTER}" measure (run-autofix-review-verification.sh:1216) after branch code has already run on the host (the agent step; a no-sandbox relaunch path exists when QWEN_SANDBOX_IMAGE is empty), but no digest is recorded at staging and none is verified before execution — unlike the sibling staged copies protected by this same step (resanitize_sha256/verify_runner_sha256 recorded in GITHUB_OUTPUT at yml:3899-3903, checked with sha256sum -c at 5512/5772, af-111). Round 22 re-verified at this commit: the staging step still records only the resanitize / verify_runner / heartbeat digests; the counter has none. The very actor this gate exists to police can defeat it.
Witness:
Round-21 end-to-end through the real gate with a planted launderer counter:
CAND5_HONEST_STATUS=1 ('round weakened pre-existing tests without recorded evidence')
CAND5_EVIL_STATUS=0, outcome=fixed, rejection ''
— same staged-counter path, same manifest, whole-file deletion laundered
grep of the workflow at this commit: no counter digest exists anywhere
(only resanitize_sha256 / verify_runner_sha256 / heartbeat digests)
Suggested fix — follow the af-111 sibling pattern: record weaken_counter_sha256=$(sha256sum "${RUNNER_TEMP}/count-test-surface.mjs" | cut -d' ' -f1) in the stage step's GITHUB_OUTPUT (guarded by -f, like the heartbeat), pass it into the gate step's env, and sha256sum -c it as close to the node invocation as possible; skip (not fail) when the digest is empty so base-absence still degrades to UNAVAILABLE.
The digest must travel via GITHUB_OUTPUT (expression context, unreachable from a disk write after staging — the doctrine at yml:3893-3899), never via a file on disk; the gate launches under env -i with a hard-coded allowlist (yml ~5518), so a digest consumed inside the gate would need adding to it — verifying at step level before launch avoids that. Please extend "stages the counter beside the gate runner, from the trusted base" to assert the stage step records the digest and the gate verifies it before node "${WEAKEN_COUNTER}" runs; removing either side must go red.
中文说明
仍然存在(沿用第 21 轮编号):暂存计数器——弱化门的测量权威——在分支代码已运行之后经 node "${WEAKEN_COUNTER}" measure 执行,但暂存时未记录摘要、执行前也不校验——不同于同一步骤受 af-111 保护的兄弟副本(yml:3899-3903 记录、5512/5772 校验)。第 22 轮在本提交复核:暂存步骤仍只记录 resanitize / verify_runner / heartbeat 三个摘要,计数器没有。门要防范的对象恰好能绕过它。
证人证据:见英文版(植入洗白计数器端到端:诚实计数器拒绝、植入计数器放行且连整文件删除也被洗白;本提交 grep:不存在任何计数器摘要)。
建议修复:照 af-111 兄弟模式——暂存步骤以 -f 守卫把 weaken_counter_sha256 写入 GITHUB_OUTPUT,传入门步骤 env,在贴近 node 调用处 sha256sum -c;摘要为空时跳过(不是失败),保持 base 缺失退化为 UNAVAILABLE。摘要必须经由 GITHUB_OUTPUT(表达式上下文,暂存后的磁盘写入不可达),绝不落盘;门以 env -i 白名单启动,若在门内消费摘要需加入白名单——在步骤级启动前校验可避免。请把「记录摘要 + 执行前校验」钉进暂存测试;移除任一侧须变红。
— qwen3.8-max via Qwen Code /review (v0.23.0)
| done < <(git diff --name-only -z --no-renames --diff-filter=D "origin/${BRANCH}" "${BRANCH}" \ | ||
| -- "${WEAKEN_PATHSPEC[@]}" 2> /dev/null) |
There was a problem hiding this comment.
[Critical] R21-9: [certifies-falsely] [new-surface] Still stands (carried from round 21): the UNAVAILABLE arm's deletion enumeration filters on --diff-filter=D only, which misses typechange (T): replacing an existing test file with a symlink removes its entire declared surface without producing a deletion entry, so the arm fails open for exactly the class its contract says it never fails open for ("Fails OPEN on the measured ... and never on a whole-file deletion" — the same pre-round->tip pair proves a typechange). The measured branch catches this shape (per-commit enumeration has no filter; the tip blob parses as the symlink's target text and yields a negative delta), so the hole exists only in this degraded arm — which the suite itself exercises via noCounter: true and any walk/counter failure reaches in production. The code is unchanged since round 21.
Witness:
Round-21 probes:
Git-level: T pkg/a.test.ts under --name-status;
--diff-filter=D -> (empty);
--diff-filter=DT -> pkg/a.test.ts
End-to-end gate (existing symlink fixture through noCounter:true):
UNMODIFIED: status=0, rejection '' — round accepted with pkg/a.test.ts now a symlink
WITH --diff-filter=DT: status=1, rejection contains 'test file deleted'
the path-based exemption checks work unchanged on the T entry
Suggested fix — widen the degraded enumeration:
done < <(git diff --name-only -z --no-renames --diff-filter=DT "origin/${BRANCH}" "${BRANCH}" \
-- "${WEAKEN_PATHSPEC[@]}" 2> /dev/null)The arm must stay deletion-judging only (header contract at run-autofix-review-verification.sh:1093-1096) — widening the filter must not start measuring content signals without the walk. Please extend "judges deletions even when the measurement is unavailable" (~line 25506) with a fixture whose round swaps a pathspec file for a symlink under noCounter: true, asserting status 1 with "test file deleted"; today it passes green at status 0.
中文说明
仍然存在(沿用第 21 轮编号):UNAVAILABLE 分支的删除枚举只按 --diff-filter=D 过滤,漏掉类型变更(T):把既有测试文件替换为符号链接会移除其全部声明表面却不产生删除条目,于是该分支恰好在契约声称「绝不放行」的类别上放行(「测量信号放行……整文件删除绝不放行」——轮前→tip 对同样能证明类型变更)。测量分支能抓住该形态(逐提交枚举无过滤),漏洞只在这个退化分支——套件自己用 noCounter: true 演练它,生产中任何走查/计数器失败都会到达。代码自第 21 轮未变。
证人证据:见英文版探针(--diff-filter=D 为空、--diff-filter=DT 列出该路径;端到端:原代码 status=0 放行已变符号链接的文件,改 DT 后 status=1 拒绝)。
建议修复(代码块见英文):把退化枚举放宽为 --diff-filter=DT。该分支必须仅做删除判定(抬头契约 :1093-1096),放宽过滤不得开始测量内容信号。请把「无测量时也判删除」测试扩展一个符号链接替换夹具(noCounter: true),断言 status 1 且含 'test file deleted';今天它以 status 0 绿着。
— qwen3.8-max via Qwen Code /review (v0.23.0)
| # a counter that cannot run skips them with a logged UNAVAILABLE -- and | ||
| # never on a whole-file deletion, which the pre-round->tip pair proves | ||
| # without the walk. | ||
| WEAKEN_PATHSPEC=(':(glob)**/*.test.*' ':(glob)**/*.spec.*' ':(glob)**/test_*.py' ':(glob)**/tests/*.rs' ':(glob)**/*_test.rs' ':(glob)**/*_tests.rs' ':(exclude,glob)**/__snapshots__/**') |
There was a problem hiding this comment.
[Critical] R18-2: [certifies-falsely] [new-surface] Still stands (carried id from round 20): the name-based WEAKEN_PATHSPEC still cannot see a Rust module whose tests are an inline #[cfg(test)] mod tests block — 207 Rust src/*.rs files carrying inline #[test] functions were measured unenumerated at round 20, and no name-based pathspec can select them. A round that deletes or guts such a module is accepted with no ack and no advisory, while the gate's CONTRACT promises every pre-existing test file whose declared surface shrinks must be named in test-weakening.json. The header line documents the boundary ("outside this gate by design"), which makes the limitation deliberate — but a documented limitation is not a closure of the blocker's structural demand (ask the runner which files it collected, and apply that set to both the rejecting pathspec and the advisory's), and the demonstrated harm still fires at the reviewed commit. The pathspec still carries only the six test-named glob arms at this commit.
Witness:
Round-20 measurement (git ls-files driven with the pathspec as authority):
paths matched by WEAKEN_PATHSPEC: 2507
Rust src/*.rs modules carrying inline #[test], unenumerated: 207
Code age at round 22: the pathspec at line 1097 still carries only the six
test-named glob arms; no runner-derived enumeration was added.
Fix direction: derive the measured set from the runner (the vitest/cargo collection output) and apply it to both WEAKEN_PATHSPEC and the advisory's TEST_PATHSPEC — or, at minimum, add a cargo-test-derived enumeration for src/*.rs modules carrying #[cfg(test)] blocks.
The fix must respect the header invariant at run-autofix-review-verification.sh:1140-1143 — "The rejecting gate must not see strictly less of the repo's test surface than the advisory's TEST_PATHSPEC above" — so a runner-derived set must be applied to both arrays. Please pin it: the committed "rejects deleting a Rust test file named *_tests.rs" and "rejects deleting a non-JS test file the JS pathspec missed" pins must stay status 1, and a new fixture deleting a src/*.rs file whose only test content is an inline #[cfg(test)] mod tests must reach status 1 — today the pathspec cannot select it at all.
中文说明
仍然存在(沿用第 20 轮编号):按文件名选取的 WEAKEN_PATHSPEC 依然看不见「测试内容是内联 #[cfg(test)] mod tests 块」的 Rust 模块:第 20 轮实测有 207 个携带内联 #[test] 的 Rust src/*.rs 文件未被枚举,且任何按名的 pathspec 都无法选中它们。一轮修复删除或掏空这样的模块会在既无 ack 也无 advisory 的情况下被接受,而门的 CONTRACT 承诺任何声明表面缩水的既有测试文件都必须写进 test-weakening.json。头部注释把该边界写成了设计(「本门之外,设计上如此」),使限制成为有意为之——但「写入注释」不等于满足本阻塞项的结构性诉求(询问运行器实际收集了哪些文件,并把该集合同时应用到拒绝性 pathspec 与 advisory 的 TEST_PATHSPEC),且已验证的危害在被审提交上仍然成立。本提交上 pathspec 仍只有六个测试命名分支。
证人证据:见英文版第 20 轮实测(匹配 2507 条路径;未枚举的内联 #[test] Rust 模块 207 个)与第 22 轮代码年龄测量(仍未新增运行器推导枚举)。
修复方向:以运行器推导的集合(vitest/cargo 的收集输出)为准,同时应用到 WEAKEN_PATHSPEC 与 advisory 的 TEST_PATHSPEC;至少为携带 #[cfg(test)] 块的 src/*.rs 模块加入 cargo 推导的枚举。修复须尊重 :1140-1143 的抬头不变量(拒绝性门看到的测试面不得严格少于 advisory 的 TEST_PATHSPEC)。请补钉子:既有两个删除拒绝测试保持 status 1;新增「删除唯一测试内容为内联 #[cfg(test)] mod tests 的 src/*.rs 文件」夹具须达 status 1——今天 pathspec 根本选不中它。
— qwen3.8-max via Qwen Code /review (v0.23.0)
| # from the head prepare classified: the premise is unknown, never green. | ||
| PUSH_PRE="${CHECK_STATE:-none}" | ||
| [[ "${PUSH_RACE_MERGED}" == 'true' ]] && PUSH_PRE='none' | ||
| echo "<!-- autofix-push round=${NEXT_ROUND} head=${PUSHED_HEAD} pre=${PUSH_PRE} key=${WINDOW:-none} -->" |
There was a problem hiding this comment.
[Critical] R22-1: [certifies-falsely] [new-surface] The new autofix-push seed marker rides solely in the report body: when the push landed but the report post fails, no fallback posts it (POST_HANDOFF excludes OUTCOME=fixed, yml:6092), so the regression af-155 exists to charge can never be charged. Round N: prepare classifies the head green (CHECK_STATE=green), OUTCOME=fixed, the push loop lands H_N. gh pr comment then fails all 3 bounded attempts (the API-brownout case the retry loop exists for) and the script exits 1 at [[ "${REPORT_POSTED}" == 'true' ]] || exit 1 (~line 741). The 'Report dry-run / failure' step does run (its if: includes failure()), but POST_HANDOFF stays false because its outer guard requires OUTCOME != fixed/noop — so round N posts nothing: no eval marker, no push marker. The marker block exists only in the never-posted report.md. If the pushed fix turned checks red, no future prepare can charge it: the charge requires the newest persisted push marker's head= to equal the live head (yml:4786-4790), and no marker naming H_N was ever persisted; once any later bot push lands, the marker that would have matched is no longer sort_by(.at) | last anyway, closing the window permanently. The diff's own failure-path addition (yml ~6660-6665) records autofix-regression there "or a run of failures after a regressing push would lose it entirely", but its premise comment "A failed round pushed nothing" is false for this shape — a round can fail at reporting after pushing — and it deliberately writes no push marker. Contrast: the sibling autofix-regression marker has cross-round redundancy — every later prepare re-detects and re-records the charge — the push marker has none.
Witness:
Traced hop by hop at the reviewed commit:
emitter: autofix-push-and-report.sh:655 (sole, inside report.md; grep: no other emitter)
exit: "[[ \"${REPORT_POSTED}\" == 'true' ]] || exit 1" after 3 attempts
yml:6092 guard "OUTCOME != fixed && != noop" excludes the fixed-outcome post-failure shape
charge conjunct requires LP_HEAD==CHECKED_OUT_HEAD — no marker, no charge
the post-failure envelope is not locally runnable without a live GitHub API,
but the control flow is deterministic once it occurs
Suggested fix — make the seed record survive independently of the full report post: right after the push loop (before the post attempts), write round_pushed=true and pushed_head=$(git rev-parse HEAD) to $GITHUB_OUTPUT so the state escapes the dying step; then when REPORT_POSTED is false and ROUND_PUSHED is true, best-effort post a minimal marker-only comment (autofix-eval + autofix-push) — or extend the failure-report path to cover OUTCOME=fixed with steps.push_report failed, writing the push marker there.
The workflow file is within a few KB of the repo's 470,000-byte gate and GitHub stops starting runs past 512,000 without saying so (script lines 5-9) — prefer a script-side fallback over adding bytes to qwen-autofix.yml; and the POST_HANDOFF fixed/noop exclusion (yml:6092) is deliberate per af-138 — a fix routed through it must not start posting handoff comments for successfully-reported fixed/noop rounds. The af-155 suite needs a case "push succeeds, report post fails → push marker still reaches the PR and the next prepare charges that round when checks are red"; it must go red if the fallback post is removed.
中文说明
新的 autofix-push 种子标记只搭载在报告正文里:推送成功但报告发布失败时没有任何兜底发布它(POST_HANDOFF 排除 OUTCOME=fixed,yml:6092),于是 af-155 要计费的回归永远无法被计费。轮次 N:prepare 分类 head 为 green,OUTCOME=fixed,推送循环落地 H_N;gh pr comment 三次有界重试全部失败(重试循环正是为 API 故障窗口而设),脚本在 [[ "${REPORT_POSTED}" == 'true' ]] || exit 1(约 741 行)退出 1。'Report dry-run / failure' 步骤确实会运行(其 if: 含 failure()),但 POST_HANDOFF 因其外层守卫要求 OUTCOME != fixed/noop 而保持 false——轮次 N 什么都不发:没有 eval 标记,也没有 push 标记。标记块只存在于从未发出的 report.md。若推送的修复把检查弄红,未来任何 prepare 都无法计费:计费要求最新持久化 push 标记的 head= 等于活 head(yml:4786-4790),而没有任何标记写着 H_N;一旦后续任何 bot 推送落地,本可匹配的标记也不再是 sort_by(.at) | last,窗口被永久关闭。diff 自己的失败路径补充(yml ~6660-6665)在那里写 autofix-regression「否则回归推送之后的一串失败会彻底丢失记录」,但其前提注释「失败的轮次没有推送」对这种形态是假的——轮次可以在推送之后才失败于发布——且它刻意不写 push 标记。对照:兄弟 autofix-regression 标记有跨轮冗余——每个后续 prepare 都会重新探测并重新记录计费——push 标记没有。
证人证据:见英文版在被审提交上的逐跳追踪(唯一发射器在 report.md 内;三次重试后 exit 1;yml:6092 守卫排除 fixed 结果的发布失败形态;计费合取式要求 LP_HEAD==CHECKED_OUT_HEAD——无标记则无计费;该故障窗口无法在本地无 GitHub API 复现,但控制流一旦进入即为确定)。
建议修复:让种子记录独立于完整报告发布而存活——推送循环之后、发布尝试之前,把 round_pushed=true 与 pushed_head=$(git rev-parse HEAD) 写入 $GITHUB_OUTPUT,使状态逃出垂死步骤;当 REPORT_POSTED 为 false 且 ROUND_PUSHED 为 true 时,尽力发布一条仅含标记的最小评论(autofix-eval + autofix-push)——或把失败报告路径扩展到 steps.push_report 失败时的 OUTCOME=fixed,在那里写 push 标记。workflow 文件距离仓库 470,000 字节门只有几 KB,GitHub 在超过 512,000 时会静默停止启动运行(脚本 5-9 行)——优先脚本侧兜底而不是给 qwen-autofix.yml 加字节;POST_HANDOFF 的 fixed/noop 排除(yml:6092)是 af-138 的刻意设计——经由它的修复不得开始为成功报告的 fixed/noop 轮次发布 handoff 评论。af-155 套件需要一个用例「推送成功、报告发布失败 → push 标记仍到达 PR,且检查转红时下一轮 prepare 计费该轮」;移除兜底发布须使其变红。
— qwen3.8-max via Qwen Code /review (v0.23.0)
| while IFS= read -r -d '' f; do | ||
| [[ -n "${f}" ]] || continue | ||
| if git cat-file -e "${PR_BASE}:${f}" 2> /dev/null && | ||
| ! git cat-file -e "origin/main:${f}" 2> /dev/null; then |
There was a problem hiding this comment.
[Critical] R22-2: [certifies-falsely] [new-surface] The UNAVAILABLE arm's freight exemption is unsound: "present at PR_BASE, gone from origin/main" proves main deleted the file at some point after the merge base — not that this round adopted that deletion via a merge — so a round that directly deletes a stale pre-existing test file is silently exempted whenever main happens to have deleted it earlier, contradicting the gate's own "never fails open on a whole-file deletion" invariant (lines 1093-1096) and the arm's own fail-closed fallback when PR_BASE degrades (an unresolvable merge base degrades PR_BASE to origin/main, "which makes the exemption unsatisfiable: every deletion is then surfaced rather than dropped"). The measured arm charges this exact sequence (pre ≠ null → baselinePresent=true → test file deleted), so the degraded arm fails open on precisely the signal the header claims the pre-round->tip pair proves "without the walk".
Failure shape: measurement is UNAVAILABLE — from history-walk failure (parentless round commit), counter crash, or enumeration refusal; each structurally reachable, rare, and exactly when the exemption misbehaves. Main deletes test file F after this PR's merge base (the routine stale-base shape); the branch has not merged that deletion, so origin/${BRANCH} still holds F. The round deletes F outright. git diff origin/${BRANCH}..${BRANCH} --diff-filter=D lists F; the exemption sees PR_BASE:F exists and origin/main:F does not, continues, and the deletion never reaches WEAKENED_PATHS — no rejection, no ack, no advisory.
Witness:
Driven through the real gate script via the PR's own runGate harness
(onMain seeds pkg/a.test.ts; main deletes it after the fork; the round
deletes it directly with git rm, never merging main):
F8 MEASURED status=1 rejection="**round weakened pre-existing tests..." - pkg/a.test.ts — test file deleted
F8 UNAVAILABLE status=0 rejection="" unavailableNotice=true
Flip check: exemption disabled (mutant 'if false; then continue; fi' over lines 1297-1299):
F8 UNAVAILABLE status=1 with the deletion charged under the UNAVAILABLE notice
Suggested fix — in the UNAVAILABLE arm, surface every pre-round→tip deletion: drop the exemption, or apply it only when freight is provable; freight is ackable via test-weakening.json with evidence, matching the arm's own stated preference when the merge base is unresolvable ("every deletion is then surfaced rather than dropped").
The header invariant "Fails OPEN on the measured signals ... and never on a whole-file deletion" (lines 1093-1096) and the arm's fail-closed degradation comment (lines 1289-1291) require the fix to stay fail-closed under uncertainty. Please add a UNAVAILABLE-arm case (counter absent) where main deletes a test file after the merge base, the branch stays behind, and the round deletes it directly; it must assert the path is charged and go red while the exemption drops it.
中文说明
UNAVAILABLE 分支的 freight 豁免不健全:「在 PR_BASE 存在、在 origin/main 消失」只能证明 main 在合并基之后的某个时刻删除了该文件——不能证明本轮通过合并采纳了那次删除——于是只要 main 恰好早先删过,轮次直接删除一个陈旧的既有测试文件就会被静默豁免,与门自身「整文件删除绝不放行」的不变量(1093-1096 行)以及该分支在 PR_BASE 退化时的失败关闭兜底(合并基不可解析时 PR_BASE 退化为 origin/main,「豁免变得不可满足:每次删除都被列出而不是丢弃」)相矛盾。测量分支对完全相同的序列计费(pre ≠ null → baselinePresent=true → test file deleted),所以退化分支恰在抬头声称「无需走查即可证明」的信号上放行。
失败形态:测量处于 UNAVAILABLE——历史走查失败(无父轮次提交)、计数器崩溃或枚举被拒;每一种都结构性可达、罕见、且恰是豁免出错之时。main 在本 PR 合并基之后删除测试文件 F(常规陈旧基形态);分支未合并该删除,origin/${BRANCH} 仍持有 F;轮次直接删除 F。枚举起出 F;豁免看到 PR_BASE:F 存在而 origin/main:F 不存在,continue,删除永远进不了 WEAKENED_PATHS——无拒绝、无 ack、无 advisory。
证人证据:见英文版通过 PR 自带 runGate 驱动真实门脚本的输出(测量分支计费、UNAVAILABLE 分支放行;禁用豁免的变异体使 UNAVAILABLE 分支也计费,探针翻转)。
建议修复:在 UNAVAILABLE 分支列出全部轮前→tip 删除——去掉豁免,或仅在可证明 freight 时应用;freight 可以经 test-weakening.json 附证据 ack,与该分支在合并基不可解析时的自述偏好一致。修复必须保持不确定时失败关闭(抬头不变量 1093-1096 与退化注释 1289-1291)。请补一个 UNAVAILABLE 用例(计数器缺失):main 在合并基后删除测试文件、分支落后、轮次直接删除;断言该路径被计费,且在豁免仍在时变红。
— qwen3.8-max via Qwen Code /review (v0.23.0)
| | IN("FAILURE", "FAILED", "ERROR", "TIMED_OUT", "ACTION_REQUIRED")) then "red" | ||
| elif any($c[]; ((.status // "") | IN("QUEUED", "IN_PROGRESS", "WAITING", "PENDING")) | ||
| or ((.conclusion // .state // "") | IN("PENDING", "", "EXPECTED", "STALE", "STARTUP_FAILURE"))) then "pending" | ||
| else "green" end' "${WORKDIR}/checks.json" 2> /dev/null || echo 'none')" |
There was a problem hiding this comment.
[Critical] R22-3: [certifies-falsely] [new-surface] The head-state classifier maps a CANCELLED check to green — the charge-enabling state — because CANCELLED matches neither the red list (line 4768) nor the pending list (lines 4769-4770) and falls to else "green". Three lines above, the diff's own invariant reads: "A check with no verdict (EXPECTED: a required context that never reported; STALE; a run that never started) is pending, never green." A cancelled run produced no pass/fail verdict on the head — it is the runtime sibling of STARTUP_FAILURE, which the classifier itself parks in pending. The pin at scripts/tests/qwen-autofix-workflow.test.js:26037 (.toBe('green') for CANCELLED) justifies the mapping as consistency with the scan's N_RED_NOW filter (yml:3376-3380, excludes CANCELLED) — but not-red does not entail green; the classifier could agree with N_RED_NOW and still say pending.
Concrete timeline, executed through the extracted classifier block: a PR head's CI run is cancelled (manual cancel, infra); the scan's N_FAILED_CHECKS counts CANCELLED as a failure (yml ~3366), so the loop stays engaged on exactly such heads; an address round's prepare classifies the pre-push head — CANCELLED falls to else "green", so the push marker stamps pre=green — a premise that the head was verified clean, which no run ever established. The bot pushes; fresh CI on the new head surfaces the pre-existing failure the cancelled run had masked; next prepare's four facts (LP_HEAD==CHECKED_OUT_HEAD, pre=green, window, CHECK_STATE=red) all hold and charge the prior round; the brake walk increments CONSEC_FAIL for the charged round — billing a round for a red it did not author. af-155's documented residual ("A genuinely flaky check is the residual") does not cover this shape: a flake passes at push time and flips later; here nothing ever passed at push time.
Witness:
Extracted workflow block executed across two rounds (fresh fixture dirs):
BASELINE: STAGE A (head w/ CANCELLED suite, rest SUCCESS): check_state=green
SCAN N_FAILED_CHECKS on the same rollup: 1 (>0 selects the PR for a round)
STAGE B (next prepare, head red, marker pre=green): regressed_round=3
RESULT: round 3 CHARGED — the premise was stamped green off a no-verdict head
FIX (add "CANCELLED" to the pending arm): STAGE A check_state=pending;
STAGE B regressed_round=(none) — no charge
CONTROL (observing round sees CANCELLED, not FAILURE): check_state=green —
the documented rationale (cancelled is not red) is preserved by the fix
Suggested fix:
or ((.conclusion // .state // "") | IN("PENDING", "", "EXPECTED", "STALE", "STARTUP_FAILURE", "CANCELLED"))) then "pending"and flip the pinned test row to .toBe('pending'), correcting the comment (not-red ≠ green).
The classifier's red list must stay CANCELLED-free to match N_RED_NOW — yml:3378 IN("FAILURE", "FAILED", "ERROR", "TIMED_OUT", "ACTION_REQUIRED"), pinned by scripts/tests/qwen-autofix-workflow.test.js:1696-1701 ("no CANCELLED: a cancelled check is not a persistent red state") — so the fix must go through the pending arm, not the red arm. The flipped test row is the fix witness: it goes red if CANCELLED is removed from the pending arm; the existing pushMarker({ pre: 'pending' }) charge row already pins that a pending premise blocks the charge.
中文说明
head 状态分类器把 CANCELLED 检查映射为 green——计费启用态——因为 CANCELLED 既不在红列表(4768 行)也不在 pending 列表(4769-4770 行),落入 else "green"。三行之上,diff 自己的不变量写着:「没有判决的检查(EXPECTED:从未上报的必需上下文;STALE;从未启动的运行)是 pending,绝不是 green」。被取消的运行未在 head 上产生任何通过/失败判决——它是 STARTUP_FAILURE 的运行时兄弟,而分类器自己把 STARTUP_FAILURE 放进 pending。scripts/tests/qwen-autofix-workflow.test.js:26037 的钉子(CANCELLED → .toBe('green'))以「与扫描的 N_RED_NOW 过滤器一致」为由(yml:3376-3380 排除 CANCELLED)——但「非红」不蕴含「绿」;分类器完全可以与 N_RED_NOW 一致而仍说 pending。
具体时间线(经提取的分类器块执行):PR head 的 CI 运行被取消(人工/基础设施);扫描的 N_FAILED_CHECKS 把 CANCELLED 计为失败(yml ~3366),循环恰在这类 head 上保持介入;address 轮次的 prepare 分类推送前的 head——CANCELLED 落入 else "green",push 标记盖上 pre=green——一个「head 已被验证干净」的前提,而没有任何运行确立过它。bot 推送;新 head 的全新 CI 暴露被取消运行掩盖的既有失败;下一轮 prepare 的四个事实(LP_HEAD==CHECKED_OUT_HEAD、pre=green、窗口、CHECK_STATE=red)全部成立,计费上一轮;熔断走查对被计费轮递增 CONSEC_FAIL——为一笔并非它造成的红记账。af-155 文档化的残余(「真正 flaky 的检查是残余」)不覆盖此形态:flake 在推送时通过、之后翻转;这里推送时从未有任何东西通过。
证人证据:见英文版(提取块双轮执行:基线计费、修复后不计费、对照保留「取消非红」的设计理由;翻转成立)。
建议修复(代码块见英文):把 "CANCELLED" 加入 pending 分支,并把测试行翻转为 .toBe('pending'),修正注释(非红 ≠ 绿)。分类器的红列表必须保持不含 CANCELLED 以匹配 N_RED_NOW——yml:3378,由 test:1696-1701 钉住——因此修复必须走 pending 分支而非红分支。翻转后的测试行即修复见证:从 pending 分支移除 CANCELLED 会使其变红;既有的 pushMarker({ pre: 'pending' }) 用例行已钉住 pending 前提阻断计费。
— qwen3.8-max via Qwen Code /review (v0.23.0)
|
🔀 Base updated: red check(s) [Dependency CVE audit] pass on current main — merged current main via update-branch; CI will re-run. 中文说明🔀 已更新 base:红色检查 [Dependency CVE audit] 在当前 main 上通过 —— 已通过 update-branch 合入当前 main,CI 将重新运行。 |
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
Partially reviewed — gaps disclosed.
4 Suggestion-level finding(s) this review confirmed are already reported on this PR and are not repeated:
- [[ -s ]] empty auto-merge conflation — restates R21-4 at .github/scripts/run-autofix-review-verification.sh:1183 (already reported)
- optionsDisable fails-key gap — restates R21-2 entrance (e) at .github/scripts/count-test-surface.mjs:237 (already reported)
- expression-spelled return guard bypass — restates R21-2 entrance (d) at .github/scripts/count-test-surface.mjs:343 (already reported)
- baselineEnabled event-absorption — restates R21-1 entrance (5) at .github/scripts/count-test-surface.mjs:386 (already reported)
Not reviewed: build-and-test — Test (macos-latest, Node 22.x) was skipped in CI (does not run on pull_request events); the suite ran locally on Linux — the bash-3.2 lane's native behavior was not exercised.
Not reviewed: the executable-script lint — .github/scripts/autofix-push-and-report.sh: shellcheck is not installed.
Not reviewed: the executable-script lint — .github/scripts/run-autofix-review-verification.sh: shellcheck is not installed.
Not linted (tool limitation, not a blocker): .github/workflows/qwen-autofix.yml — actionlint embedded-shell source mapping is not yet supported.
Deferred under the convergence posture (round 23, not a blocker) — recorded, not requested in this round; 1 Critical(s) among them are deferred by their axes — fails-closed on new surface, where no wrong result is certified and the merge base had neither the surface nor the defect — and remain follow-up work recorded in the findings artifact:
.github/workflows/qwen-autofix.yml:3875 — [probe] Critical [fails-closed] [new-surface] the trusted-base staging step cps count-test-surface.mjs without the || true guard — every pre-merge round whose workflow resolves from the PR's own ref….github/scripts/count-test-surface.mjs:199 — [probe] truthyLiteral/falsyLiteral miss statically decidable literal forms — skipIf(-1)/runIf(NaN) flip runtime state while the gate measures no change.github/scripts/run-autofix-review-verification.sh:1350 — [probe] the ack matcher compares raw-path base64 while the rejection renders the lossy safe-character set — non-safe-set paths can never ackscripts/tests/qwen-autofix-workflow.test.js:9769 — [probe] PR_ROLLUP's fetch-side || echo '{}' fallback is pinned nowhere — dropping it aborts prepare under bash -e with the suite greenscripts/tests/qwen-autofix-workflow.test.js:25286 — [probe] the bash-3.2 guard's regex net omits ;& and ${var,}/${var^} (bash-4-only) — a future gate edit using one passes the guard while the macOS lane diesscripts/tests/qwen-autofix-workflow.test.js:25782 — [probe] the count CLI mode has zero production consumers (the gate only invokes measure) — dead surface pinned under a title claiming the gate uses itscripts/tests/qwen-autofix-workflow.test.js:25293 — [probe] the tab-name ack test drives a status-0 flow without the hasBashMapfile host gate — vacuously green on bash 3.2scripts/tests/qwen-autofix-workflow.test.js:25285 — [probe] the bash-3.2 pin's scan window ends at the first mapfile — a mapfile introduced in the gate region truncates the window and escapes the regex
Convergence: round 23 posted 14 inline comment(s), 1 of them reported for the first time; the previous round posted 13 (3 new). Findings keep coming back to the same files: .github/scripts/run-autofix-review-verification.sh (findings in rounds 18, 21, 22; 1 more now). A cluster that keeps producing siblings usually means the fixes are treating instances of a shared root cause — triaging that cause before the next round, or splitting an independent cluster into its own pull request, tends to end the loop faster than fixing them one at a time. (Observation only — nothing was withheld from this review because of this observation.)
Mechanism health: this round did not close cleanly, so it withholds the incremental anchor — and the round it recovered had no anchor this round could use either — none at all, one with no certifier, one certified by an identity other than the one this round runs under, or one this round's fetch refused or resolved to the head — so the next review re-reads the whole diff unless recovery grafts an earlier own anchor that the round running it can use onto the complete work list this round leaves behind, and keeps doing so until a round's marker carries an anchor again or a graft lands that the round running it can use. (Stated, not acted on — this changes nothing about what the round posts.)
中文说明
仅完成部分审查,审查缺口已披露。
本轮确认的 4 条建议级发现已在 PR 上报告过,不再重复发布(列表见上方英文部分)。
未审查(原文为英文):build-and-test — Test (macos-latest, Node 22.x) was skipped in CI (does not run on pull_request events); the suite ran locally on Linux — the bash-3.2 lane's native behavior was not exercised.
未审查(原文为英文):the executable-script lint — .github/scripts/autofix-push-and-report.sh: shellcheck is not installed.
未审查(原文为英文):the executable-script lint — .github/scripts/run-autofix-review-verification.sh: shellcheck is not installed.
未检查(工具限制,非阻断):.github/workflows/qwen-autofix.yml——actionlint 对 workflow 内嵌 shell 的源映射尚未支持。
收敛姿态下延后(第 23 轮,非阻断)——已记录,本轮不要求修改;其中 1 条 Critical 按其失败方向与对照基线延后——fails-closed 且 new-surface:未认证任何错误结果,且 merge base 既无该功能面也无该缺陷——作为后续工作记录在 findings 工件中:共 8 条(原文未翻译,列表见上方英文部分)。
收敛情况:第 23 轮发布了 14 条行内评论,其中 1 条是首次提出;上一轮发布了 13 条(其中 3 条首次提出)。发现反复回到同一批文件:.github/scripts/run-autofix-review-verification.sh(第 18、21、22 轮已出过发现,本轮又有 1 条)。一个不断再生兄弟发现的簇,通常意味着逐条修复只在处理同一根因的实例——先定位并处理该根因,或把独立的簇拆成单独的 PR,通常比逐条修复更快结束循环。(仅为观察——本轮评审未因此扣留任何内容。)
机制健康:本轮未能干净收尾,因而扣留了增量锚点,而它恢复到的那一轮也没有留下本轮可用的锚点——要么完全没有、要么没有认证者、要么由本轮运行身份之外的身份认证、要么被本轮的获取拒绝或解析为头提交——因此下一次评审将重读整个 diff,除非恢复流程把本轮能使用的更早自有锚点嫁接到本轮留下的完整工作清单上;并会一直如此,直到某一轮的标记重新带上锚点,或落地的嫁接能被运行该轮的评审使用。(仅陈述,不据此行动——这不改变本轮发布的任何内容。)
— qwen3.8-max via Qwen Code /review (v0.23.0)
| const afterEnabled = new Set(after.enabledTitles); | ||
| for (const k of after.enabledTitles) baselineEnabled.add(k); | ||
| for (const k of before.enabledTitles) { | ||
| if (!afterEnabled.has(k)) mainDisabled.add(k); |
There was a problem hiding this comment.
[Critical] R21-1: [certifies-falsely] [new-surface] Still stands (carried from round 21): class-level finding — measure() attributes by title-keyed Set membership and position-free net deltas with no per-registration identity, multiplicity, or event pairing. The seven round-21 entrances stand unchanged (the PR has pushed no code since): (1) mainDisabled is add-only across events — the stale shield lets a round disabling a re-enabled title escape with newlyDisabled:[] plus one balancing test; (2) enabled counts describe/suite wrappers like tests; (3) position-free kind:title collisions; (4) title-granular, not multiplicity-aware, shielding; (5) merge after.enabledTitles leaks the round's own registrations into the baseline; (6) a negative event delta is credited unconditionally; (7) renamed-then-disabled escapes.
This round demonstrates one further entrance at this commit: the enabled arithmetic is kind-blind (test and describe registrations are fungible) and newlyDisabled only sees keys present in the tip's disabled list, so converting test('x', body) into describe('x', same body) deletes the runtime test case with every signal at zero — vitest registers zero test cases for the describe body, and under the gate's own --passWithNoTests flag the empty suite passes, so the weakening is end-to-end silent.
Witness:
Round-22 probe at this commit (code byte-identical to round 21):
measure(test('x') -> describe('x')) => {"assertions":0,"guards":0,"enabled":0,"newlyDisabled":[]}
control outright deletion => {"assertions":-1,"enabled":-1}
vitest: converted.test.js (0 test) passes with the gate's --passWithNoTests flag
Round-21 witness, entrance (1): stale-shield escape => newlyDisabled:[]
with mainDisabled.delete(k) fix => newlyDisabled:["test:a"]
Fix direction: replace the title-set algebra with per-registration, multiplicity-aware attribution — mainDisabled as a Map of key→count built as the multiset difference of before/after enabledTitles per event (re-enabled titles consume shield counts); baselineEnabled extended only with titles main itself introduced; newlyDisabled as the multiset difference of tip-disabled against baseline-disabled and the shield counts; title-similarity pairing for renamed-then-disabled; and make enabled kind-aware so a test→describe conversion nets −1 enabled test.
The fix must keep newlyDisabled an array whose length equals the number of registrations charged — run-autofix-review-verification.sh:1273 reads (.newlyDisabled | length) to build the charge text — and the header contract at count-test-surface.mjs:46-53 ("main's contribution neither charges nor shields") is the acceptance bar. Please pin each arm, including a measure case asserting test('x') → describe('x') reports a removal; removing any arm of the fix must redden exactly its own scenario while the existing single-event scenarios stay green.
Round-23 re-verification at e20bddb (three entrances re-executed, one further entrance demonstrated): entrance (1) — the two-event sequence still returns newlyDisabled:[] and flips with mainDisabled.delete(k); entrance (7) — renamed-then-disabled with one balancing enabled test still measures all-zero (the control without the rename charges); entrances (3)/(4) charge direction — a merge-only round is rejected "1 pre-existing test registration(s) disabled" for main's own disabling of ONE of two same-title registrations, and a multiplicity-aware flip restores status 0 while a genuine round disable still charges. New entrance this round: assertions emptied from a live test and re-housed in a brand-new it.skip measure all-zero on every verdict arm (fix: count assertions only inside enabled registrations — flips to "net 2 assertion(s) removed").
中文说明
仍然存在(沿用第 21 轮编号):类级问题——measure() 按「标题键集合成员关系」与「无位置净差值」归属,没有按注册项的身份、重数与事件配对。第 21 轮的七个入口原样成立(PR 此后未推送任何代码):(1) mainDisabled 跨事件只增不减——陈旧豁免让「重新启用后再禁用」的轮次以 newlyDisabled:[] 加一个平衡测试逃逸;(2) enabled 把 describe/suite 包装器当测试计数;(3) 无位置的 kind:title 键碰撞;(4) 豁免按标题粒度而非重数;(5) merge 事件的 after.enabledTitles 把轮次自己的注册泄漏进基线;(6) 事件负差值无条件计信;(7) 改名后禁用可逃逸。
本轮在本提交上验证了一个新入口:enabled 运算不区分 kind(test 与 describe 可互换),且 newlyDisabled 只看 tip 禁用列表中出现的键,因此把 test('x', body) 改写成 describe('x', same body) 会在所有信号为零的情况下删除运行时测试——vitest 对 describe 体不注册任何测试用例,而门自己的 --passWithNoTests 标志让空套件通过,弱化全程静默。
证人证据:见英文版探针输出(本提交执行;代码与第 21 轮逐字节一致)。
修复方向:以按注册项、重数感知的归属取代标题集合代数(mainDisabled 改为键→计数 Map,按事件做多重集差,被重新启用的标题消耗豁免计数;baselineEnabled 只并入 main 自己引入的标题;newlyDisabled 按多重集差计算;引入标题相似度配对;并让 enabled 区分 kind,使 test→describe 转换净 −1)。修复须保持 newlyDisabled 数组长度等于被计费注册数(run-autofix-review-verification.sh:1273 读取其长度生成计费文本),验收标准是 count-test-surface.mjs:46-53 的头部契约。请为每个入口补钉子,包括断言 test→describe 报告删除的 measure 用例;移除修复任一部分须恰好使对应场景变红,既有单事件场景保持绿色。
— qwen3.8-max via Qwen Code /review (v0.23.0)
| registrations.push({ | ||
| kind: ROOTS[chain.root] ?? XROOTS[chain.root], | ||
| title: titleOf(last, sf), | ||
| disabled: registrationDisabled(chain), |
There was a problem hiding this comment.
[Critical] R21-2: [certifies-falsely] [new-surface] Still stands (carried from round 21): class-level finding — count() decides registration, disabling and guard attribution by chain-root identifier name and syntactic shape, with no binding awareness and no control-flow modeling. The five round-21 entrances (plus three low-confidence siblings) stand unchanged: parenthesized/unary wrappers never peeled by truthyLiteral/falsyLiteral/optionsDisable; isBodySkip ignoring enclosing control flow; positional containment across the nested-function boundary; fn:null callbacks and the phantom {kind:'test',title:''} registration; phantom title-key collisions. The surface cannot be completed entrance by entrance — every spelling fix reopens on the next JS construct.
This round demonstrates SEVEN further entrances, each probed at this commit:
(a) optionsDisable decodes only the literal true keyword, but vitest truthy-checks options — it('a', { skip: 'flaky under load' }, fn) silently disables while the counter measures it enabled;
(b) a disabled suite (describe.skip) never propagates disabling to nested registrations — a no-op .skip inside an already-skipped suite false-charges, and describe→describe.skip under-charges;
(c) the guard walk aborts (lastAssertion < 0) when a test's assertions live in a module-level helper — an early-return weakening there is invisible;
(d) the guard signal counts only bare return; — return undefined; / return void 0; / return null; bypass by spelling;
(e) optionsDisable recognises only skip/todo while vitest's TestOptions.fails inverts the verdict — { fails: true } pins the OPPOSITE behaviour with zero signal, while the .fails chain member IS classified;
(f) a body-level ctx.skip() in a flat file's top-level beforeEach records the inert phantom entry while vitest skips EVERY test in the file;
(g) assertion detection accepts only ExpressionStatement/ReturnStatement/arrow-body positions and chainOf peels only NonNull/Paren mid-chain — assertions executed from ternary/logical/comma/prefix-unary/yield positions, or cut off by a mid-chain as/satisfies cast, count zero; deleting them measures all-zero.
Witness:
Probes at this commit (unmodified counter vs pinned vitest 3.2.7):
(a) {skip:'flaky under load'} counts enabled:1 == PRE; vitest: 1 passed | 1 skipped, exit 0
(d) insert 'return;' -> guards=1, signal fires; insert 'return undefined;' -> guards=0, EMPTY
(e) measure(pre=it('a',fn), tip=it('a',{fails:true},fn)) all-zero;
vitest passes the throwing body under {fails:true}
(f) flat file + beforeEach(ctx=>ctx.skip()): measure all-zero; vitest: Tests 2 skipped (2)
(g) seven conditional-position assertions -> assertions:0; their deletion measures all-zero
The class fix round 21 asked for stands: replace name/shape attribution with runner-grounded semantics (the runner's own collected-test enumeration, or per-construct modeling of vitest's truthiness/focus/fails/options and nesting). Per-entrance spelling fixes will reopen on the next construct — rounds 12 to 22 of this PR's own history are the evidence. Please pin each demonstrated entrance; removing any arm of the fix must redden exactly its own row. Condition-valued guards (skipIf(cond), skip(cond, reason)) must stay uncharged per the NOT MEASURED contract (run-autofix-review-verification.sh:1085-1090), and assertion-carrying returns must keep counting the assertion and not a guard.
Round-23 re-verification at e20bddb against the pinned vitest 3.2.7: entrance (a) re-executed — it('a', { skip: 'flaky until lands' }, fn) never runs at runtime while the counter measures it enabled; entrance (d) re-executed — return undefined; / return void 0; / return null; all yield guards:0 vs bare return; guards:1; entrance (e) re-executed — { fails: true } passes when its body throws while measured enabled (the .fails member spelling of the same edit IS flagged); control-flow entrance re-executed with live citations (packages/cli/src/commands/review/lib/same-file.test.ts:85, packages/core/src/config/config.test.ts:7167) — the conditional ctx.skip() test RAN while measured disabled, and converting it to it.skip measures all-zero. New entrance this round: the literal skipIf/runIf evaluators miss PrefixUnaryExpression (-1/+1), identifiers NaN/Infinity, BigIntLiteral and parenthesized forms — skipIf(-1)/runIf(NaN) measure enabled while the runtime flips them.
中文说明
仍然存在(沿用第 21 轮编号):类级问题——count() 仅凭链根标识符名与语法形状判定注册、禁用与守卫归属,不做绑定分析、不建模控制流。第 21 轮的五个入口(加三个低置信兄弟形态)原样成立:字面量判定不剥括号/一元包装;isBodySkip 不看外层控制流;跨嵌套函数边界的纯位置包含;fn:null 回调与幻影注册;幻影标题键碰撞。该检测面无法逐入口补完——每修一种写法都会被下一种 JS 构造重新打开。
本轮在本提交上验证了七个新入口:(a) optionsDisable 只解码字面量 true,而 vitest 对 options 做真假值判定——{ skip: '理由字符串' } 静默禁用却被测为启用;(b) 禁用的套件不向嵌套注册传播——已跳过套件里的无操作 .skip 造成误记账,describe→describe.skip 记账不足;(c) 断言位于模块级辅助函数时守卫遍历中止,提前 return 弱化不可见;(d) 守卫信号只数裸 return;——return undefined;/void 0/null 按拼写绕过;(e) optionsDisable 只认 skip/todo,而 vitest 的 TestOptions.fails 反转判决——{ fails: true } 零信号钉住相反行为(链成员 .fails 却被分类);(f) 平铺文件顶层 beforeEach 里的 ctx.skip() 只产生惰性的幻影条目,而 vitest 跳过全文件测试;(g) 断言检测只接受 ExpressionStatement/ReturnStatement/箭头体位置,链中 as/satisfies 截断根——三元/逻辑/逗号/前缀一元/yield 位置的断言计零,删除它们测得全零。
证人证据:见英文版探针输出(未改动计数器与钉住的 vitest 3.2.7 对照,本提交执行)。
第 21 轮请求的类级修复仍然成立:以运行器背书的语义取代按名/形状归属(运行器自己的收集清单,或对 vitest 真假值/only/fails/options 与嵌套的逐构造建模)。逐入口修补会被下一种构造重新打开——本 PR 第 12–22 轮的历史就是证据。请为每个已验证入口补钉子;移除修复任一部分须恰好使自己的行变红。条件式守卫(skipIf(cond)、skip(cond, reason))按 NOT MEASURED 契约保持不计费;携带断言的 return 须继续计断言而不计守卫。
— qwen3.8-max via Qwen Code /review (v0.23.0)
| # The branch holds no blob: main added the file and it lands; or the | ||
| # branch deleted it earlier and main's edit is a modify/delete conflict | ||
| # resolved for the branch's deletion. | ||
| [[ -z "${base}" ]] && printf '%s\n' "${p2}" |
There was a problem hiding this comment.
[Critical] R21-3: [certifies-falsely] [new-surface] Still stands (carried from round 21): a round's own deletion of a pre-existing test file is silently exempted when a main-derived merge follows the deletion and main's side still holds the file. At the merge commit the first-parent diff is quiet (absent vs absent) but the second-parent diff differs, so an event is recorded; weaken_auto_blob hits this p1-empty branch with a non-empty base and returns nothing; the event becomes {before: null, after: null}; baselinePresent flips false; the shell takes the "not the round's to weaken" branch and accepts — although the file was held by the pre-round baseline and is absent at the tip, against this section's stated "never on a whole-file deletion" contract. The code is unchanged since round 21.
Witness:
Round-21 end-to-end through the real runGate harness:
PR: CAND2_STATUS=0, outcome=fixed, rejection '' (deletion exempted)
fix ([[ -n "${before}" || -n "${after}" ]] || continue before events+=):
CAND2_STATUS=1, rejection '- pkg/a.test.ts — test file deleted'
four attribution suites stay green under the fix
Suggested fix — skip recording events where both before and after are empty; they carry no main delta and their only effect is the baselinePresent flip:
[[ -n "${before}" || -n "${after}" ]] || continueThe fix must not break count-test-surface.mjs:384 (baselinePresent = ev.after !== null), the only mechanism by which a MAIN deletion exempts a round (pinned by the merge-delete-freight fixture) — the skip may fire only when before is also empty. Please pin it with a WEAKEN_FIXTURES entry (round git rms F, mainMoves edits F, merge resolved for the deletion) asserting rejection with signal "test file deleted"; removing the skip guard must turn it red.
Round-23 end-to-end re-verification at e20bddb through the real gate (fixture: round git rms pkg/a.test.ts, main edits it, merge resolved for the deletion): UNMODIFIED exempts the deletion (WEAKENED_PATHS empty, baselinePresent:false); the proposed skip guard flips to REJECT_FIX "test file deleted" while the freight case (main deletes, round adopts) stays exempt.
中文说明
仍然存在(沿用第 21 轮编号):当轮次自己删除既有测试文件、随后发生 main 派生合并且 main 侧仍持有该文件时,轮次自己的删除被静默豁免:合并提交上第一父 diff 静默(缺失对缺失)而第二父 diff 有差异,于是记录了事件;weaken_auto_blob 走到 p1 为空、base 非空的分支并返回空;事件成为 {before: null, after: null};baselinePresent 翻为 false;shell 走「非本轮可弱化」分支直接接受——尽管文件在轮前基线存在、在 tip 缺失,违反本节「整文件删除绝不放行」的契约。代码自第 21 轮未变。
证人证据:见英文版 runGate 真实门端到端输出(原代码放行;修复后以 'test file deleted' 拒绝,四个归属套件保持绿色)。
建议修复:跳过 before 与 after 均为空的事件(代码块见英文)。约束:不得破坏 count-test-surface.mjs:384——它是 MAIN 删除豁免轮次的唯一机制(由 merge-delete-freight 夹具钉住),跳过只能在 before 同为空时触发。请补夹具(轮次 git rm F、mainMoves 编辑 F、合并按删除方向解决)断言以 'test file deleted' 拒绝;移除跳过守卫须变红。
— qwen3.8-max via Qwen Code /review (v0.23.0)
| # Conflicts resolve for the branch (--ours). Only a hard failure (binary | ||
| # content) leaves the output empty, and then the branch's side stands. | ||
| git merge-file -p --ours "${p1}" "${base}" "${p2}" > "${out}" 2> /dev/null || true | ||
| [[ -s "${out}" ]] || cp "${p1}" "${out}" |
There was a problem hiding this comment.
[Critical] R21-4: [certifies-falsely] [new-surface] Still stands (carried from round 21): [[ -s "${out}" ]] conflates a legitimate EMPTY auto-merge result with a merge-file hard failure. When main empties a test file (keeps the file, deletes all content) and the round merges main, git merge-file -p --ours succeeds with rc=0 and 0-byte output; the -s test then copies the branch's pre-merge content over main's side, main's delta measures 0, and the round is rejected retryably with "net N assertion(s) removed" — a false charge for main's edit, contradicting the comment above's claim that only a hard failure leaves the output empty. Re-measured at this commit by round 22.
Witness:
Round-22 measurement at this commit:
git merge-file -p --ours with main's side truncated -> rc=0, out size 0 (legitimate empty success)
[[ -s ]] false -> cp p1 fallback fires
Round-21 end-to-end fixture (main empties F, round merges main unchanged):
PR: CAND3_STATUS=1, rejection '- pkg/a.test.ts — net 2 assertion(s) removed'
Fix flip (fall back to p1 only on nonzero exit): CAND3_STATUS=0, outcome=fixed
Constraint: NUL-byte (binary) input -> rc=255 with empty output, so the fallback must survive for nonzero exit
Suggested fix — capture the exit status and fall back to the branch side only on failure, never on an empty success:
weaken_merge_rc=0
git merge-file -p --ours "${p1}" "${base}" "${p2}" > "${out}" 2> /dev/null || weaken_merge_rc=$?
if (( weaken_merge_rc != 0 )) && [[ ! -s "${out}" ]]; then
cp "${p1}" "${out}"
fiThe branch-side fallback must stay for nonzero exit (binary content exits 255 with empty stdout; a --ours conflict merge exits 0 with output, so an exit-status-based fallback is safe). Please pin it with a WEAKEN_FIXTURES entry where main empties F and the round merges main unchanged, asserting the gate accepts; restoring [[ -s ]] || cp must turn it red.
Round-23 re-verification at e20bddb: git merge-file -p --ours with main's side emptied exits 0 with 0 bytes (legitimate empty success) and the -s fallback substitutes the branch side; binary input exits 255 (the fallback must survive there); conflicting text under --ours exits 0 with output — so an exit-status-keyed fallback is safe.
中文说明
仍然存在(沿用第 21 轮编号):[[ -s "${out}" ]] 把「合法的空自动合并结果」与「merge-file 硬失败」混为一谈。当 main 清空测试文件(保留文件、删除全部内容)且轮次合并 main 时,git merge-file -p --ours 以 rc=0 输出 0 字节成功;-s 判定随后把分支侧内容覆盖到 main 侧,main 增量测得 0,轮次被以「net N assertion(s) removed」可重试地拒绝——为 main 的编辑向轮次误计费,与上方注释「只有硬失败才留下空输出」矛盾。第 22 轮在本提交上复测确认。
证人证据:见英文版(本提交实测 + 第 21 轮端到端夹具:原代码误拒;仅非零退出兜底后接受;二进制输入 rc=255 约束)。
建议修复(代码块见英文):捕获退出码,仅在失败时回退分支侧。请补夹具(main 清空 F、轮次原样合并 main)断言门接受;还原 [[ -s ]] || cp 须变红。
— qwen3.8-max via Qwen Code /review (v0.23.0)
| cp .github/scripts/check-autofix-contracts.sh "${RUNNER_TEMP}/check-autofix-contracts.sh" | ||
| cp .github/scripts/resolve-owning-packages.sh "${RUNNER_TEMP}/resolve-owning-packages.sh" | ||
| cp .github/scripts/run-autofix-review-verification.sh "${RUNNER_TEMP}/run-autofix-review-verification.sh" | ||
| cp .github/scripts/count-test-surface.mjs "${RUNNER_TEMP}/count-test-surface.mjs" |
There was a problem hiding this comment.
[Critical] R21-5: [certifies-falsely] [new-surface] Still stands (carried from round 21): the staged counter — the measurement authority of the new test-weakening gate — is executed via node "${WEAKEN_COUNTER}" measure (run-autofix-review-verification.sh:1216) after branch code has already run on the host (the agent step; a no-sandbox relaunch path exists when QWEN_SANDBOX_IMAGE is empty), but no digest is recorded at staging and none is verified before execution — unlike the sibling staged copies protected by this same step (resanitize_sha256/verify_runner_sha256 recorded in GITHUB_OUTPUT at yml:3899-3903, checked with sha256sum -c at 5512/5772, af-111). Round 22 re-verified at this commit: the staging step still records only the resanitize / verify_runner / heartbeat digests; the counter has none. The very actor this gate exists to police can defeat it.
Witness:
Round-21 end-to-end through the real gate with a planted launderer counter:
CAND5_HONEST_STATUS=1 ('round weakened pre-existing tests without recorded evidence')
CAND5_EVIL_STATUS=0, outcome=fixed, rejection ''
— same staged-counter path, same manifest, whole-file deletion laundered
grep of the workflow at this commit: no counter digest exists anywhere
(only resanitize_sha256 / verify_runner_sha256 / heartbeat digests)
Suggested fix — follow the af-111 sibling pattern: record weaken_counter_sha256=$(sha256sum "${RUNNER_TEMP}/count-test-surface.mjs" | cut -d' ' -f1) in the stage step's GITHUB_OUTPUT (guarded by -f, like the heartbeat), pass it into the gate step's env, and sha256sum -c it as close to the node invocation as possible; skip (not fail) when the digest is empty so base-absence still degrades to UNAVAILABLE.
The digest must travel via GITHUB_OUTPUT (expression context, unreachable from a disk write after staging — the doctrine at yml:3893-3899), never via a file on disk; the gate launches under env -i with a hard-coded allowlist (yml ~5518), so a digest consumed inside the gate would need adding to it — verifying at step level before launch avoids that. Please extend "stages the counter beside the gate runner, from the trusted base" to assert the stage step records the digest and the gate verifies it before node "${WEAKEN_COUNTER}" runs; removing either side must go red.
Round-23 re-verification at e20bddb with a planted launderer counter through the real gate: the honest counter rejects ("round weakened pre-existing tests without recorded evidence"); the planted zero-delta stub is accepted with outcome=fixed — weakening laundered while the gate believes it measured. Sweep at this commit: digests are recorded for resanitize/verify-runner/heartbeat only; the counter has none.
中文说明
仍然存在(沿用第 21 轮编号):暂存计数器——弱化门的测量权威——在分支代码已运行之后经 node "${WEAKEN_COUNTER}" measure 执行,但暂存时未记录摘要、执行前也不校验——不同于同一步骤受 af-111 保护的兄弟副本(yml:3899-3903 记录、5512/5772 校验)。第 22 轮在本提交复核:暂存步骤仍只记录 resanitize / verify_runner / heartbeat 三个摘要,计数器没有。门要防范的对象恰好能绕过它。
证人证据:见英文版(植入洗白计数器端到端:诚实计数器拒绝、植入计数器放行且连整文件删除也被洗白;本提交 grep:不存在任何计数器摘要)。
建议修复:照 af-111 兄弟模式——暂存步骤以 -f 守卫把 weaken_counter_sha256 写入 GITHUB_OUTPUT,传入门步骤 env,在贴近 node 调用处 sha256sum -c;摘要为空时跳过(不是失败),保持 base 缺失退化为 UNAVAILABLE。摘要必须经由 GITHUB_OUTPUT(表达式上下文,暂存后的磁盘写入不可达),绝不落盘;门以 env -i 白名单启动,若在门内消费摘要需加入白名单——在步骤级启动前校验可避免。请把「记录摘要 + 执行前校验」钉进暂存测试;移除任一侧须变红。
— qwen3.8-max via Qwen Code /review (v0.23.0)
| # a counter that cannot run skips them with a logged UNAVAILABLE -- and | ||
| # never on a whole-file deletion, which the pre-round->tip pair proves | ||
| # without the walk. | ||
| WEAKEN_PATHSPEC=(':(glob)**/*.test.*' ':(glob)**/*.spec.*' ':(glob)**/test_*.py' ':(glob)**/tests/*.rs' ':(glob)**/*_test.rs' ':(glob)**/*_tests.rs' ':(exclude,glob)**/__snapshots__/**') |
There was a problem hiding this comment.
[Critical] R18-2: [certifies-falsely] [new-surface] Still stands (carried id from round 20): the name-based WEAKEN_PATHSPEC still cannot see a Rust module whose tests are an inline #[cfg(test)] mod tests block — 207 Rust src/*.rs files carrying inline #[test] functions were measured unenumerated at round 20, and no name-based pathspec can select them. A round that deletes or guts such a module is accepted with no ack and no advisory, while the gate's CONTRACT promises every pre-existing test file whose declared surface shrinks must be named in test-weakening.json. The header line documents the boundary ("outside this gate by design"), which makes the limitation deliberate — but a documented limitation is not a closure of the blocker's structural demand (ask the runner which files it collected, and apply that set to both the rejecting pathspec and the advisory's), and the demonstrated harm still fires at the reviewed commit. The pathspec still carries only the six test-named glob arms at this commit.
Witness:
Round-20 measurement (git ls-files driven with the pathspec as authority):
paths matched by WEAKEN_PATHSPEC: 2507
Rust src/*.rs modules carrying inline #[test], unenumerated: 207
Code age at round 22: the pathspec at line 1097 still carries only the six
test-named glob arms; no runner-derived enumeration was added.
Fix direction: derive the measured set from the runner (the vitest/cargo collection output) and apply it to both WEAKEN_PATHSPEC and the advisory's TEST_PATHSPEC — or, at minimum, add a cargo-test-derived enumeration for src/*.rs modules carrying #[cfg(test)] blocks.
The fix must respect the header invariant at run-autofix-review-verification.sh:1140-1143 — "The rejecting gate must not see strictly less of the repo's test surface than the advisory's TEST_PATHSPEC above" — so a runner-derived set must be applied to both arrays. Please pin it: the committed "rejects deleting a Rust test file named *_tests.rs" and "rejects deleting a non-JS test file the JS pathspec missed" pins must stay status 1, and a new fixture deleting a src/*.rs file whose only test content is an inline #[cfg(test)] mod tests must reach status 1 — today the pathspec cannot select it at all.
Round-23 re-check at e20bddb: the pathspec at line 1097 still carries only the six test-named glob arms; no runner-derived enumeration was added.
中文说明
仍然存在(沿用第 20 轮编号):按文件名选取的 WEAKEN_PATHSPEC 依然看不见「测试内容是内联 #[cfg(test)] mod tests 块」的 Rust 模块:第 20 轮实测有 207 个携带内联 #[test] 的 Rust src/*.rs 文件未被枚举,且任何按名的 pathspec 都无法选中它们。一轮修复删除或掏空这样的模块会在既无 ack 也无 advisory 的情况下被接受,而门的 CONTRACT 承诺任何声明表面缩水的既有测试文件都必须写进 test-weakening.json。头部注释把该边界写成了设计(「本门之外,设计上如此」),使限制成为有意为之——但「写入注释」不等于满足本阻塞项的结构性诉求(询问运行器实际收集了哪些文件,并把该集合同时应用到拒绝性 pathspec 与 advisory 的 TEST_PATHSPEC),且已验证的危害在被审提交上仍然成立。本提交上 pathspec 仍只有六个测试命名分支。
证人证据:见英文版第 20 轮实测(匹配 2507 条路径;未枚举的内联 #[test] Rust 模块 207 个)与第 22 轮代码年龄测量(仍未新增运行器推导枚举)。
修复方向:以运行器推导的集合(vitest/cargo 的收集输出)为准,同时应用到 WEAKEN_PATHSPEC 与 advisory 的 TEST_PATHSPEC;至少为携带 #[cfg(test)] 块的 src/*.rs 模块加入 cargo 推导的枚举。修复须尊重 :1140-1143 的抬头不变量(拒绝性门看到的测试面不得严格少于 advisory 的 TEST_PATHSPEC)。请补钉子:既有两个删除拒绝测试保持 status 1;新增「删除唯一测试内容为内联 #[cfg(test)] mod tests 的 src/*.rs 文件」夹具须达 status 1——今天 pathspec 根本选不中它。
— qwen3.8-max via Qwen Code /review (v0.23.0)
| # from the head prepare classified: the premise is unknown, never green. | ||
| PUSH_PRE="${CHECK_STATE:-none}" | ||
| [[ "${PUSH_RACE_MERGED}" == 'true' ]] && PUSH_PRE='none' | ||
| echo "<!-- autofix-push round=${NEXT_ROUND} head=${PUSHED_HEAD} pre=${PUSH_PRE} key=${WINDOW:-none} -->" |
There was a problem hiding this comment.
[Critical] R22-1: [certifies-falsely] [new-surface] The new autofix-push seed marker rides solely in the report body: when the push landed but the report post fails, no fallback posts it (POST_HANDOFF excludes OUTCOME=fixed, yml:6092), so the regression af-155 exists to charge can never be charged. Round N: prepare classifies the head green (CHECK_STATE=green), OUTCOME=fixed, the push loop lands H_N. gh pr comment then fails all 3 bounded attempts (the API-brownout case the retry loop exists for) and the script exits 1 at [[ "${REPORT_POSTED}" == 'true' ]] || exit 1 (~line 741). The 'Report dry-run / failure' step does run (its if: includes failure()), but POST_HANDOFF stays false because its outer guard requires OUTCOME != fixed/noop — so round N posts nothing: no eval marker, no push marker. The marker block exists only in the never-posted report.md. If the pushed fix turned checks red, no future prepare can charge it: the charge requires the newest persisted push marker's head= to equal the live head (yml:4786-4790), and no marker naming H_N was ever persisted; once any later bot push lands, the marker that would have matched is no longer sort_by(.at) | last anyway, closing the window permanently. The diff's own failure-path addition (yml ~6660-6665) records autofix-regression there "or a run of failures after a regressing push would lose it entirely", but its premise comment "A failed round pushed nothing" is false for this shape — a round can fail at reporting after pushing — and it deliberately writes no push marker. Contrast: the sibling autofix-regression marker has cross-round redundancy — every later prepare re-detects and re-records the charge — the push marker has none.
Witness:
Traced hop by hop at the reviewed commit:
emitter: autofix-push-and-report.sh:655 (sole, inside report.md; grep: no other emitter)
exit: "[[ \"${REPORT_POSTED}\" == 'true' ]] || exit 1" after 3 attempts
yml:6092 guard "OUTCOME != fixed && != noop" excludes the fixed-outcome post-failure shape
charge conjunct requires LP_HEAD==CHECKED_OUT_HEAD — no marker, no charge
the post-failure envelope is not locally runnable without a live GitHub API,
but the control flow is deterministic once it occurs
Suggested fix — make the seed record survive independently of the full report post: right after the push loop (before the post attempts), write round_pushed=true and pushed_head=$(git rev-parse HEAD) to $GITHUB_OUTPUT so the state escapes the dying step; then when REPORT_POSTED is false and ROUND_PUSHED is true, best-effort post a minimal marker-only comment (autofix-eval + autofix-push) — or extend the failure-report path to cover OUTCOME=fixed with steps.push_report failed, writing the push marker there.
The workflow file is within a few KB of the repo's 470,000-byte gate and GitHub stops starting runs past 512,000 without saying so (script lines 5-9) — prefer a script-side fallback over adding bytes to qwen-autofix.yml; and the POST_HANDOFF fixed/noop exclusion (yml:6092) is deliberate per af-138 — a fix routed through it must not start posting handoff comments for successfully-reported fixed/noop rounds. The af-155 suite needs a case "push succeeds, report post fails → push marker still reaches the PR and the next prepare charges that round when checks are red"; it must go red if the fallback post is removed.
Round-23 re-check at e20bddb: the sole autofix-push emitter is still inside report.md (autofix-push-and-report.sh:655); the failure-report guard still excludes OUTCOME=fixed (yml:6092); no fallback post was added.
中文说明
新的 autofix-push 种子标记只搭载在报告正文里:推送成功但报告发布失败时没有任何兜底发布它(POST_HANDOFF 排除 OUTCOME=fixed,yml:6092),于是 af-155 要计费的回归永远无法被计费。轮次 N:prepare 分类 head 为 green,OUTCOME=fixed,推送循环落地 H_N;gh pr comment 三次有界重试全部失败(重试循环正是为 API 故障窗口而设),脚本在 [[ "${REPORT_POSTED}" == 'true' ]] || exit 1(约 741 行)退出 1。'Report dry-run / failure' 步骤确实会运行(其 if: 含 failure()),但 POST_HANDOFF 因其外层守卫要求 OUTCOME != fixed/noop 而保持 false——轮次 N 什么都不发:没有 eval 标记,也没有 push 标记。标记块只存在于从未发出的 report.md。若推送的修复把检查弄红,未来任何 prepare 都无法计费:计费要求最新持久化 push 标记的 head= 等于活 head(yml:4786-4790),而没有任何标记写着 H_N;一旦后续任何 bot 推送落地,本可匹配的标记也不再是 sort_by(.at) | last,窗口被永久关闭。diff 自己的失败路径补充(yml ~6660-6665)在那里写 autofix-regression「否则回归推送之后的一串失败会彻底丢失记录」,但其前提注释「失败的轮次没有推送」对这种形态是假的——轮次可以在推送之后才失败于发布——且它刻意不写 push 标记。对照:兄弟 autofix-regression 标记有跨轮冗余——每个后续 prepare 都会重新探测并重新记录计费——push 标记没有。
证人证据:见英文版在被审提交上的逐跳追踪(唯一发射器在 report.md 内;三次重试后 exit 1;yml:6092 守卫排除 fixed 结果的发布失败形态;计费合取式要求 LP_HEAD==CHECKED_OUT_HEAD——无标记则无计费;该故障窗口无法在本地无 GitHub API 复现,但控制流一旦进入即为确定)。
建议修复:让种子记录独立于完整报告发布而存活——推送循环之后、发布尝试之前,把 round_pushed=true 与 pushed_head=$(git rev-parse HEAD) 写入 $GITHUB_OUTPUT,使状态逃出垂死步骤;当 REPORT_POSTED 为 false 且 ROUND_PUSHED 为 true 时,尽力发布一条仅含标记的最小评论(autofix-eval + autofix-push)——或把失败报告路径扩展到 steps.push_report 失败时的 OUTCOME=fixed,在那里写 push 标记。workflow 文件距离仓库 470,000 字节门只有几 KB,GitHub 在超过 512,000 时会静默停止启动运行(脚本 5-9 行)——优先脚本侧兜底而不是给 qwen-autofix.yml 加字节;POST_HANDOFF 的 fixed/noop 排除(yml:6092)是 af-138 的刻意设计——经由它的修复不得开始为成功报告的 fixed/noop 轮次发布 handoff 评论。af-155 套件需要一个用例「推送成功、报告发布失败 → push 标记仍到达 PR,且检查转红时下一轮 prepare 计费该轮」;移除兜底发布须使其变红。
— qwen3.8-max via Qwen Code /review (v0.23.0)
| if git cat-file -e "${PR_BASE}:${f}" 2> /dev/null && | ||
| ! git cat-file -e "origin/main:${f}" 2> /dev/null; then |
There was a problem hiding this comment.
[Critical] R22-2: [certifies-falsely] [new-surface] The UNAVAILABLE arm's freight exemption is unsound: "present at PR_BASE, gone from origin/main" proves main deleted the file at some point after the merge base — not that this round adopted that deletion via a merge — so a round that directly deletes a stale pre-existing test file is silently exempted whenever main happens to have deleted it earlier, contradicting the gate's own "never fails open on a whole-file deletion" invariant (lines 1093-1096) and the arm's own fail-closed fallback when PR_BASE degrades (an unresolvable merge base degrades PR_BASE to origin/main, "which makes the exemption unsatisfiable: every deletion is then surfaced rather than dropped"). The measured arm charges this exact sequence (pre ≠ null → baselinePresent=true → test file deleted), so the degraded arm fails open on precisely the signal the header claims the pre-round->tip pair proves "without the walk".
Failure shape: measurement is UNAVAILABLE — from history-walk failure (parentless round commit), counter crash, or enumeration refusal; each structurally reachable, rare, and exactly when the exemption misbehaves. Main deletes test file F after this PR's merge base (the routine stale-base shape); the branch has not merged that deletion, so origin/${BRANCH} still holds F. The round deletes F outright. git diff origin/${BRANCH}..${BRANCH} --diff-filter=D lists F; the exemption sees PR_BASE:F exists and origin/main:F does not, continues, and the deletion never reaches WEAKENED_PATHS — no rejection, no ack, no advisory.
Witness:
Driven through the real gate script via the PR's own runGate harness
(onMain seeds pkg/a.test.ts; main deletes it after the fork; the round
deletes it directly with git rm, never merging main):
F8 MEASURED status=1 rejection="**round weakened pre-existing tests..." - pkg/a.test.ts — test file deleted
F8 UNAVAILABLE status=0 rejection="" unavailableNotice=true
Flip check: exemption disabled (mutant 'if false; then continue; fi' over lines 1297-1299):
F8 UNAVAILABLE status=1 with the deletion charged under the UNAVAILABLE notice
Suggested fix — in the UNAVAILABLE arm, surface every pre-round→tip deletion: drop the exemption, or apply it only when freight is provable; freight is ackable via test-weakening.json with evidence, matching the arm's own stated preference when the merge base is unresolvable ("every deletion is then surfaced rather than dropped").
The header invariant "Fails OPEN on the measured signals ... and never on a whole-file deletion" (lines 1093-1096) and the arm's fail-closed degradation comment (lines 1289-1291) require the fix to stay fail-closed under uncertainty. Please add a UNAVAILABLE-arm case (counter absent) where main deletes a test file after the merge base, the branch stays behind, and the round deletes it directly; it must assert the path is charged and go red while the exemption drops it.
Round-23 re-check at e20bddb: the freight exemption at lines 1297-1299 is unchanged — 'present at PR_BASE, gone from origin/main' still continues without proof the round adopted the deletion.
中文说明
UNAVAILABLE 分支的 freight 豁免不健全:「在 PR_BASE 存在、在 origin/main 消失」只能证明 main 在合并基之后的某个时刻删除了该文件——不能证明本轮通过合并采纳了那次删除——于是只要 main 恰好早先删过,轮次直接删除一个陈旧的既有测试文件就会被静默豁免,与门自身「整文件删除绝不放行」的不变量(1093-1096 行)以及该分支在 PR_BASE 退化时的失败关闭兜底(合并基不可解析时 PR_BASE 退化为 origin/main,「豁免变得不可满足:每次删除都被列出而不是丢弃」)相矛盾。测量分支对完全相同的序列计费(pre ≠ null → baselinePresent=true → test file deleted),所以退化分支恰在抬头声称「无需走查即可证明」的信号上放行。
失败形态:测量处于 UNAVAILABLE——历史走查失败(无父轮次提交)、计数器崩溃或枚举被拒;每一种都结构性可达、罕见、且恰是豁免出错之时。main 在本 PR 合并基之后删除测试文件 F(常规陈旧基形态);分支未合并该删除,origin/${BRANCH} 仍持有 F;轮次直接删除 F。枚举起出 F;豁免看到 PR_BASE:F 存在而 origin/main:F 不存在,continue,删除永远进不了 WEAKENED_PATHS——无拒绝、无 ack、无 advisory。
证人证据:见英文版通过 PR 自带 runGate 驱动真实门脚本的输出(测量分支计费、UNAVAILABLE 分支放行;禁用豁免的变异体使 UNAVAILABLE 分支也计费,探针翻转)。
建议修复:在 UNAVAILABLE 分支列出全部轮前→tip 删除——去掉豁免,或仅在可证明 freight 时应用;freight 可以经 test-weakening.json 附证据 ack,与该分支在合并基不可解析时的自述偏好一致。修复必须保持不确定时失败关闭(抬头不变量 1093-1096 与退化注释 1289-1291)。请补一个 UNAVAILABLE 用例(计数器缺失):main 在合并基后删除测试文件、分支落后、轮次直接删除;断言该路径被计费,且在豁免仍在时变红。
— qwen3.8-max via Qwen Code /review (v0.23.0)
| elif any($c[]; (.conclusion // .state // "") | ||
| | IN("FAILURE", "FAILED", "ERROR", "TIMED_OUT", "ACTION_REQUIRED")) then "red" | ||
| elif any($c[]; ((.status // "") | IN("QUEUED", "IN_PROGRESS", "WAITING", "PENDING")) | ||
| or ((.conclusion // .state // "") | IN("PENDING", "", "EXPECTED", "STALE", "STARTUP_FAILURE"))) then "pending" |
There was a problem hiding this comment.
[Critical] R22-3: [certifies-falsely] [new-surface] The head-state classifier maps a CANCELLED check to green — the charge-enabling state — because CANCELLED matches neither the red list (line 4768) nor the pending list (lines 4769-4770) and falls to else "green". Three lines above, the diff's own invariant reads: "A check with no verdict (EXPECTED: a required context that never reported; STALE; a run that never started) is pending, never green." A cancelled run produced no pass/fail verdict on the head — it is the runtime sibling of STARTUP_FAILURE, which the classifier itself parks in pending. The pin at scripts/tests/qwen-autofix-workflow.test.js:26037 (.toBe('green') for CANCELLED) justifies the mapping as consistency with the scan's N_RED_NOW filter (yml:3376-3380, excludes CANCELLED) — but not-red does not entail green; the classifier could agree with N_RED_NOW and still say pending.
Concrete timeline, executed through the extracted classifier block: a PR head's CI run is cancelled (manual cancel, infra); the scan's N_FAILED_CHECKS counts CANCELLED as a failure (yml ~3366), so the loop stays engaged on exactly such heads; an address round's prepare classifies the pre-push head — CANCELLED falls to else "green", so the push marker stamps pre=green — a premise that the head was verified clean, which no run ever established. The bot pushes; fresh CI on the new head surfaces the pre-existing failure the cancelled run had masked; next prepare's four facts (LP_HEAD==CHECKED_OUT_HEAD, pre=green, window, CHECK_STATE=red) all hold and charge the prior round; the brake walk increments CONSEC_FAIL for the charged round — billing a round for a red it did not author. af-155's documented residual ("A genuinely flaky check is the residual") does not cover this shape: a flake passes at push time and flips later; here nothing ever passed at push time.
Witness:
Extracted workflow block executed across two rounds (fresh fixture dirs):
BASELINE: STAGE A (head w/ CANCELLED suite, rest SUCCESS): check_state=green
SCAN N_FAILED_CHECKS on the same rollup: 1 (>0 selects the PR for a round)
STAGE B (next prepare, head red, marker pre=green): regressed_round=3
RESULT: round 3 CHARGED — the premise was stamped green off a no-verdict head
FIX (add "CANCELLED" to the pending arm): STAGE A check_state=pending;
STAGE B regressed_round=(none) — no charge
CONTROL (observing round sees CANCELLED, not FAILURE): check_state=green —
the documented rationale (cancelled is not red) is preserved by the fix
Suggested fix:
or ((.conclusion // .state // "") | IN("PENDING", "", "EXPECTED", "STALE", "STARTUP_FAILURE", "CANCELLED"))) then "pending"and flip the pinned test row to .toBe('pending'), correcting the comment (not-red ≠ green).
The classifier's red list must stay CANCELLED-free to match N_RED_NOW — yml:3378 IN("FAILURE", "FAILED", "ERROR", "TIMED_OUT", "ACTION_REQUIRED"), pinned by scripts/tests/qwen-autofix-workflow.test.js:1696-1701 ("no CANCELLED: a cancelled check is not a persistent red state") — so the fix must go through the pending arm, not the red arm. The flipped test row is the fix witness: it goes red if CANCELLED is removed from the pending arm; the existing pushMarker({ pre: 'pending' }) charge row already pins that a pending premise blocks the charge.
Round-23 re-verification at e20bddb: the real classifier jq on [SUCCESS, CANCELLED] returns green; the dispatch filter (yml:3365) counts CANCELLED as a failure, so the loop stays engaged on exactly such heads; SKIPPED and NEUTRAL conclusions fall through the same way.
中文说明
head 状态分类器把 CANCELLED 检查映射为 green——计费启用态——因为 CANCELLED 既不在红列表(4768 行)也不在 pending 列表(4769-4770 行),落入 else "green"。三行之上,diff 自己的不变量写着:「没有判决的检查(EXPECTED:从未上报的必需上下文;STALE;从未启动的运行)是 pending,绝不是 green」。被取消的运行未在 head 上产生任何通过/失败判决——它是 STARTUP_FAILURE 的运行时兄弟,而分类器自己把 STARTUP_FAILURE 放进 pending。scripts/tests/qwen-autofix-workflow.test.js:26037 的钉子(CANCELLED → .toBe('green'))以「与扫描的 N_RED_NOW 过滤器一致」为由(yml:3376-3380 排除 CANCELLED)——但「非红」不蕴含「绿」;分类器完全可以与 N_RED_NOW 一致而仍说 pending。
具体时间线(经提取的分类器块执行):PR head 的 CI 运行被取消(人工/基础设施);扫描的 N_FAILED_CHECKS 把 CANCELLED 计为失败(yml ~3366),循环恰在这类 head 上保持介入;address 轮次的 prepare 分类推送前的 head——CANCELLED 落入 else "green",push 标记盖上 pre=green——一个「head 已被验证干净」的前提,而没有任何运行确立过它。bot 推送;新 head 的全新 CI 暴露被取消运行掩盖的既有失败;下一轮 prepare 的四个事实(LP_HEAD==CHECKED_OUT_HEAD、pre=green、窗口、CHECK_STATE=red)全部成立,计费上一轮;熔断走查对被计费轮递增 CONSEC_FAIL——为一笔并非它造成的红记账。af-155 文档化的残余(「真正 flaky 的检查是残余」)不覆盖此形态:flake 在推送时通过、之后翻转;这里推送时从未有任何东西通过。
证人证据:见英文版(提取块双轮执行:基线计费、修复后不计费、对照保留「取消非红」的设计理由;翻转成立)。
建议修复(代码块见英文):把 "CANCELLED" 加入 pending 分支,并把测试行翻转为 .toBe('pending'),修正注释(非红 ≠ 绿)。分类器的红列表必须保持不含 CANCELLED 以匹配 N_RED_NOW——yml:3378,由 test:1696-1701 钉住——因此修复必须走 pending 分支而非红分支。翻转后的测试行即修复见证:从 pending 分支移除 CANCELLED 会使其变红;既有的 pushMarker({ pre: 'pending' }) 用例行已钉住 pending 前提阻断计费。
— qwen3.8-max via Qwen Code /review (v0.23.0)
| if ! git rev-parse -q --verify "${c}^" > /dev/null 2>&1; then | ||
| WEAKEN_MEASURED='false' |
There was a problem hiding this comment.
[Critical] R23-1: [certifies-falsely] [new-surface] The history walk self-aborts into the UNAVAILABLE fallback on a parentless (root) commit in the round range — a history shape the reviewed branch itself authors — which waives the gate's entire non-deletion evidence requirement. A merge-base-preserving orphan shape reaches it: an orphan side branch carrying the weakened test becomes the FIRST PARENT of the round tip (git checkout --orphan + git merge --allow-unrelated-histories); the pre-round head stays reachable through the merge's second parent, so no force push is needed to publish. git rev-list --first-parent meets the parentless root, the walk flips WEAKEN_MEASURED='false', and the UNAVAILABLE arm judges only whole-file deletions — deleting assertions, disabling registrations, or adding early-return guards inside surviving files produces zero signal and the round is accepted with no test-weakening.json. The abort is not forced by unmeasurability: a root commit is trivially own (no event subtraction ever references c^ for kind own), so the abort converts a branch-controlled state into a blanket exemption. Note: the FULL orphan rewrite (making origin/${BRANCH} unrelated) does not reach this gate — the pre-existing unguarded DELETED_TESTS diff at line 993 dies first, fail-closed; the reachable shape is the merge-base-preserving one.
Witness:
Round-23 E2E through the repo's own runGate harness at e20bddb9:
PROBE-ORPHAN-MERGE status=0 | outputs: outcome=fixed
PROBE-ORPHAN-MERGE unavailable-line: test-weakening measurement UNAVAILABLE
this round — only whole-file deletions are judged
PROBE-ORPHAN-MERGE rejection-len=0 <- a round that cut pkg/a.test.ts from
2 assertions to 1, accepted with no test-weakening.json
FIX flip (root classified 'own', enumerated against the empty tree):
PROBE-ORPHAN-MERGE status=1 | outcome=failed retryable=true
rejection: "This round deleted or weakened pre-existing tests without
recording the required evidence: - pkg/a.test.ts — net 1 assertion(s) removed"
Full-orphan control: status=128 — crash in pre-existing unguarded code at
line 993 (fail-closed, not a waiver)
Suggested fix — do not abort on a root commit: classify it own (it cannot carry main-derived provenance), and in the candidate loop substitute a diff against the empty tree (git diff "$(git hash-object -t tree /dev/null)" "${c}") when git rev-parse -q --verify "${c}^" fails, so the round's full surface is still measured and charged. Keep the UNAVAILABLE fail-open for genuine producer failures (git errors in weaken_add_diff/weaken_measure).
The fix must keep the deletion arm intact and must not turn genuine walk/counter failures into silent passes (the gate header's fail-open contract, run-autofix-review-verification.sh:1093-1096), and the replacement must stay bash-3.2 clean (the meta-test regex-scans the gate section). Please pin it with a round range containing a parentless commit that removes assertions from a pre-existing test file — it must reject without test-weakening.json; restoring the abort makes it go red (today it logs UNAVAILABLE and accepts).
中文说明
历史走查在轮次范围内遇到无父提交(根提交)时会自行中止并退入 UNAVAILABLE 兜底——这是被审分支自己就能构造的历史形态——从而豁免了门的全部非删除证据要求。可达形态是保留合并基的孤儿结构:携带弱化测试的孤儿侧分支成为轮次 tip 的第一父(git checkout --orphan + git merge --allow-unrelated-histories),轮前 head 经合并的第二父仍可达,发布无需强推。git rev-list --first-parent 遇到无父根,走查置 WEAKEN_MEASURED='false',UNAVAILABLE 分支只判整文件删除——在存活文件内删断言、禁用注册、加提前 return 都零信号,轮次无需 test-weakening.json 即被接受。中止并非不可测量所致:根提交天然属于 own(own 类的事件差分从不引用 c^),中止把分支可控状态变成了整体豁免。注意:完全孤儿重写(令 origin/${BRANCH} 无关)到不了此门——993 行既有的无守卫 DELETED_TESTS diff 会先以失败关闭方式崩溃;可达的是保留合并基的形态。
证人证据:见英文版(runGate 真实门端到端:原代码以 outcome=fixed 接受删断言的孤儿合并轮;把根提交归类为 own 并对空树枚举后翻转为拒绝;完全孤儿对照在 993 行既有代码处以 128 失败关闭)。
建议修复:不要在根提交处中止——归类为 own,候选循环在 git rev-parse -q --verify "${c}^" 失败时改用对空树的 diff(git hash-object -t tree /dev/null),使轮次全部表面仍被测量计费;真正的生产者失败保持 UNAVAILABLE 放行。修复须保住删除分支、不得把真实的走查/计数器失败变成静默放行(抬头契约 :1093-1096),且替换实现须保持 bash-3.2 干净(有元测试正则扫描门段)。请补钉子:含无父提交的轮次范围删除既有测试的断言,须无需 test-weakening.json 即拒绝;还原中止须变红(今天它记录 UNAVAILABLE 并放行)。
— qwen3.8-max via Qwen Code /review (v0.23.0)
|
⏸️ AutoFix paused by a review convergence signal: 3 consecutive automated review round(s) signalled machine-readable non-convergence (codes observed since the last maintainer response, or the window start if none: batch-fixes, root-cause-triage) — the reviewer keeps finding new defects at a rate that is not falling while the loop keeps widening the diff, so another automatic round is unlikely to converge this PR. The loop resumes once a maintainer responds on this PR (a review or comment counts, and steers the next rounds), and pauses again if the signal persists for 3 more round(s). Alternatives: split the recurring cluster into its own PR, batch the remaining fixes into one push, comment 中文说明⏸️ AutoFix 已因评审收敛信号暂停:3 轮自动评审连续发出机器可读的不收敛信号(自上次维护者响应以来观察到的信号码;若无响应则自窗口开始:batch-fixes, root-cause-triage)——评审仍在以不降的速率发现新缺陷,而循环在继续扩大 diff,再跑一轮自动修复难以收敛本 PR。维护者在本 PR 上作出回应后循环自动恢复(评论或评审均可,并将作为后续轮次的指引);若信号再持续 3 轮会再次暂停。可选做法:把反复出问题的簇拆成独立 PR、把剩余修复攒成一批一次推送、评论 |
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
Partially reviewed — gaps disclosed.
Not reviewed: the executable-script lint — .github/scripts/autofix-push-and-report.sh: shellcheck is not installed.
Not reviewed: the executable-script lint — .github/scripts/run-autofix-review-verification.sh: shellcheck is not installed.
Not explored to full depth (tool budget reached): "agent reverse-audit (round 5)": none — wait, correcting: no Budget gap: condition occurred; all planned checks completed within budget.; "agent test-matrix": run scripts/tests/qwen-autofix-workflow.test.js under vitest — worktree has no node_modules and a full monorepo npm ci was out of budget; the surface counter ….
Not reviewed: reverse audit — stopped before round 5 by the review time budget.
Not linted (tool limitation, not a blocker): .github/workflows/qwen-autofix.yml — actionlint embedded-shell source mapping is not yet supported.
1 Suggestion(s) were drafted inline past the resolved critical posting floor; the CLI moved them into the deferral list below (floor enforcement).
Deferred under the convergence posture (round 24, not a blocker) — recorded, not requested in this round; 1 Critical(s) among them are deferred by their axes — fails-closed on new surface, where no wrong result is certified and the merge base had neither the surface nor the defect — and remain follow-up work recorded in the findings artifact:
.github/workflows/qwen-autofix.yml:3875 — [probe] Critical [fails-closed] [new-surface] D24-1 (also deferred in round 23): the trusted-base staging step cps count-test-surface.mjs without the || true guard — every pre-merge round whose work…scripts/tests/qwen-autofix-workflow.test.js:29 — [review] R24-1: The suite carrying nearly all tests this PR adds sits outside every npm workspace, so this review's workspace-scoped build/test gate ran zero suites over it (build-test: test…scripts/tests/qwen-autofix-workflow.test.js:25169 — [probe] D24-2: the host-probe audit enforces it.skipIf(!hasBashMapfile) only for a hand-enumerated title list — a NEW status-0 weakening flow test added without the probe is invisible and …scripts/tests/qwen-autofix-workflow.test.js:25288 — [probe] D24-3 (extends round-23's deferred entry): the bash-3.2-cleanliness scan regex additionally misses local -A, declare -n/local -n namerefs, negative subscripts, ${var@Q}, and bare ;….github/scripts/run-autofix-review-verification.sh:1123 — [probe] D24-4: merge classification inspects only the second parent — an octopus merge whose main-derived parent sits at position ≥3 classifies own and main's test weakening is charg…
Mechanism health: this round did not close cleanly, so it withholds the incremental anchor — and the round it recovered had no anchor this round could use either — none at all, one with no certifier, one certified by an identity other than the one this round runs under, or one this round's fetch refused or resolved to the head — so the next review re-reads the whole diff unless recovery grafts an earlier own anchor that the round running it can use onto the complete work list this round leaves behind, and keeps doing so until a round's marker carries an anchor again or a graft lands that the round running it can use. (Stated, not acted on — this changes nothing about what the round posts.)
中文说明
仅完成部分审查,审查缺口已披露。
未审查(原文为英文):the executable-script lint — .github/scripts/autofix-push-and-report.sh: shellcheck is not installed.
未审查(原文为英文):the executable-script lint — .github/scripts/run-autofix-review-verification.sh: shellcheck is not installed.
未探索到全部深度(达到工具调用预算):"agent reverse-audit (round 5)":none — wait, correcting: no Budget gap: condition occurred; all planned checks completed within budget.;"agent test-matrix":run scripts/tests/qwen-autofix-workflow.test.js under vitest — worktree has no node_modules and a full monorepo npm ci was out of budget; the surface counter …。
未审查:反向审计——评审时间预算不足,未能开始第 5 轮。
未检查(工具限制,非阻断):.github/workflows/qwen-autofix.yml——actionlint 对 workflow 内嵌 shell 的源映射尚未支持。
1 条 Suggestion 在已解析的 critical 发布下限之外被起草为行内评论;CLI 已将其移入下方延后清单(下限强制执行)。
收敛姿态下延后(第 24 轮,非阻断)——已记录,本轮不要求修改;其中 1 条 Critical 按其失败方向与对照基线延后——fails-closed 且 new-surface:未认证任何错误结果,且 merge base 既无该功能面也无该缺陷——作为后续工作记录在 findings 工件中:共 5 条(原文未翻译,列表见上方英文部分)。
机制健康:本轮未能干净收尾,因而扣留了增量锚点,而它恢复到的那一轮也没有留下本轮可用的锚点——要么完全没有、要么没有认证者、要么由本轮运行身份之外的身份认证、要么被本轮的获取拒绝或解析为头提交——因此下一次评审将重读整个 diff,除非恢复流程把本轮能使用的更早自有锚点嫁接到本轮留下的完整工作清单上;并会一直如此,直到某一轮的标记重新带上锚点,或落地的嫁接能被运行该轮的评审使用。(仅陈述,不据此行动——这不改变本轮发布的任何内容。)
— qwen3.8-max via Qwen Code /review (v0.23.0)
| # a counter that cannot run skips them with a logged UNAVAILABLE -- and | ||
| # never on a whole-file deletion, which the pre-round->tip pair proves | ||
| # without the walk. | ||
| WEAKEN_PATHSPEC=(':(glob)**/*.test.*' ':(glob)**/*.spec.*' ':(glob)**/test_*.py' ':(glob)**/tests/*.rs' ':(glob)**/*_test.rs' ':(glob)**/*_tests.rs' ':(exclude,glob)**/__snapshots__/**') |
There was a problem hiding this comment.
[Critical] R18-2: [certifies-falsely] [new-surface] Still stands (round 24): the name-based WEAKEN_PATHSPEC still cannot see a Rust module whose tests are an inline #[cfg(test)] mod tests block — 207 Rust src/*.rs files carrying inline #[test] functions were measured unenumerated at round 20, and no name-based pathspec can select them. A round that deletes or guts such a module is accepted with no ack and no advisory, while the gate's CONTRACT promises every pre-existing test file whose declared surface shrinks must be named in test-weakening.json. The header documents the boundary, but a documented limitation is not a closure of the structural demand (ask the runner which files it collected; apply that set to both the rejecting pathspec and the advisory's).
Round 24: the seven source files are byte-identical to what round 23 reviewed (the only commit since is a merge of main), so every prior entrance stands unchanged; the pathspec still carries only the six test-named glob arms.
Witness:
Round-20 measurement (pathspec as authority):
paths matched by WEAKEN_PATHSPEC: 2507
Rust src/*.rs modules carrying inline #[test], unenumerated: 207
Round-23/24 re-check: pathspec at line 1097 unchanged; no runner-derived enumeration added.
Fix direction: derive the measured set from the runner (vitest/cargo collection output) and apply it to both WEAKEN_PATHSPEC and the advisory's TEST_PATHSPEC — at minimum add a cargo-test-derived enumeration for src/*.rs modules carrying #[cfg(test)] blocks.
The fix must respect the header invariant at run-autofix-review-verification.sh:1140-1143 (the rejecting gate must not see strictly less of the repo's test surface than the advisory's TEST_PATHSPEC), so a runner-derived set must be applied to both arrays. Fix witness: the committed Rust-deletion pins stay status 1, and a new fixture deleting a src/*.rs file whose only test content is an inline #[cfg(test)] mod tests must reach status 1 — today the pathspec cannot select it at all.
中文说明
仍然存在(第 24 轮):按文件名选取的 WEAKEN_PATHSPEC 依然看不见「测试内容是内联 #[cfg(test)] mod tests 块」的 Rust 模块:第 20 轮实测有 207 个携带内联 #[test] 的 Rust src/*.rs 文件未被枚举,任何按名的 pathspec 都无法选中它们。一轮修复删除或掏空这样的模块会在既无 ack 也无 advisory 的情况下被接受,而门的 CONTRACT 承诺任何声明表面缩水的既有测试文件都必须写进 test-weakening.json。头部注释把该边界写成设计,但「写入注释」不等于满足结构性诉求(询问运行器实际收集了哪些文件,并把该集合同时应用到拒绝性 pathspec 与 advisory 的 TEST_PATHSPEC)。
第 24 轮:七个源文件与第 23 轮审查时逐字节一致(其后唯一提交是一次 main 合并),所有既有入口原样成立;pathspec 仍只有六个测试命名分支。
证人证据:见英文版(第 20 轮实测 2507/207;第 23/24 轮复查 pathspec 未变)。
修复方向:以运行器推导的集合同时应用到 WEAKEN_PATHSPEC 与 TEST_PATHSPEC;至少为携带 #[cfg(test)] 块的 src/*.rs 模块加入 cargo 推导的枚举。修复须尊重 :1140-1143 抬头不变量(两个数组同时更新)。修复见证:既有 Rust 删除钉子保持 status 1;新增「删除唯一测试内容为内联 #[cfg(test)] mod tests 的 src/*.rs 文件」夹具须达 status 1。
— qwen3.8-max via Qwen Code /review (v0.23.0)
| const afterEnabled = new Set(after.enabledTitles); | ||
| for (const k of after.enabledTitles) baselineEnabled.add(k); | ||
| for (const k of before.enabledTitles) { | ||
| if (!afterEnabled.has(k)) mainDisabled.add(k); |
There was a problem hiding this comment.
[Critical] R21-1: [certifies-falsely] [new-surface] Still stands (round 24): class-level finding — measure() attributes by title-keyed Set membership and position-free net deltas with no per-registration identity, multiplicity, or event pairing. The seven round-21 entrances stand unchanged: (1) mainDisabled is add-only across events — the stale shield lets a round disabling a re-enabled title escape with newlyDisabled:[] plus one balancing test; (2) enabled counts describe/suite wrappers like tests; (3) position-free kind:title collisions; (4) title-granular, not multiplicity-aware, shielding; (5) merge after.enabledTitles leaks the round's own registrations into the baseline; (6) a negative event delta is credited unconditionally; (7) renamed-then-disabled escapes.
Round 24 re-probed two entrances at HEAD (code byte-identical since round 21). Entrance (1): event 1 main skips it('x'), event 2 main re-enables it, round tip disables x and balances enabled with a new it('y') — all four gate arms net empty, round accepted; patching in mainDisabled.delete(k) flips to newlyDisabled:["test:x"]. Entrance (5): a round disabling a test it added itself after a merge event measures newlyDisabled:["test:b"] despite net enabled delta 0 — rejecting a clean round or forcing a false acknowledgement; absorbing only after − before and requiring the title be absent from the tip's enabled set flips it while main-adds-test-then-round-skips-it still charges.
Witness:
Round-24 probes at HEAD (unmodified measure() + the gate's jq arms):
entrance (1): buggy arm (no signal — round accepted) /
control arm signal '1 pre-existing test registration(s) disabled' /
patched arm newlyDisabled:["test:x"]
entrance (5): newlyDisabled:["test:b"], gateSignal '1 pre-existing test registration(s) disabled' (net enabled 0) /
fixed arm newlyDisabled:[], gateSignal ''
Fix direction (round 21, stands): replace the title-set algebra with per-registration, multiplicity-aware attribution — mainDisabled as a Map of key→count built as the multiset difference of before/after enabledTitles per event; baselineEnabled extended only with titles main itself introduced; newlyDisabled as the multiset difference of tip-disabled against baseline-disabled and the shield counts, requiring the title be absent from the tip's enabled set; title-similarity pairing for renamed-then-disabled; enabled kind-aware so a test→describe conversion nets −1.
The fix must keep newlyDisabled an array whose length equals the number of registrations charged (run-autofix-review-verification.sh:1273 reads (.newlyDisabled | length) for the charge text); the header contract at count-test-surface.mjs:46-53 ("main's contribution neither charges nor shields") is the acceptance bar. Fix witness: pin each arm, including a two-event case asserting a re-enabled title stops shielding and the entrance-(5) scenario asserting newlyDisabled:[]; removing any arm must redden exactly its own scenario while the existing single-event scenarios stay green.
中文说明
仍然存在(第 24 轮):类级问题——measure() 按标题键集合成员关系与无位置净差值归属,没有按注册项的身份、重数与事件配对。第 21 轮七个入口原样成立:(1) mainDisabled 跨事件只增不减;(2) enabled 把 describe/suite 包装器当测试计数;(3) 无位置的 kind:title 键碰撞;(4) 豁免按标题粒度而非重数;(5) merge 事件的 after.enabledTitles 把轮次自己的注册泄漏进基线;(6) 事件负差值无条件计信;(7) 改名后禁用可逃逸。
第 24 轮在本提交上复测两个入口(代码自第 21 轮逐字节一致):入口 (1) 两事件序列仍返回 newlyDisabled:[],补上 mainDisabled.delete(k) 后翻转;入口 (5) 轮次禁用自己合并后新增的测试被误判为弱化(净 enabled 差为 0 却计费),只吸收 after − before 并要求标题不在 tip 启用集即修复,且 main 新增测试后轮次禁用的情形仍计费。
证人证据:见英文版探针输出(本提交执行,翻转成立)。
修复方向(第 21 轮,仍成立):以按注册项、重数感知的归属取代标题集合代数(细节见英文)。修复须保持 newlyDisabled 数组长度等于被计费注册数(:1273 读取其长度),验收标准为 :46-53 头部契约。修复见证:为每个臂补钉子,包括两事件重启用例与入口 (5) 场景;移除任一部分须恰好使对应场景变红。
— qwen3.8-max via Qwen Code /review (v0.23.0)
| registrations.push({ | ||
| kind: ROOTS[chain.root] ?? XROOTS[chain.root], | ||
| title: titleOf(last, sf), | ||
| disabled: registrationDisabled(chain), |
There was a problem hiding this comment.
[Critical] R21-2: [certifies-falsely] [new-surface] Still stands (round 24): class-level finding — count() decides registration, disabling and guard attribution by chain-root identifier name and syntactic shape, with no binding awareness and no control-flow modeling. All round-21/22/23 entrances stand unchanged (code byte-identical since round 21). The surface cannot be completed entrance by entrance — every spelling fix reopens on the next JS construct; rounds 12–23 of this PR's own history are the evidence. Round 24 demonstrates TEN further entrances, each probed at this commit against the unmodified counter and vitest 3.2.7:
(a) optionsDisable decodes only literal true — it('a', { skip: 'flaky under load' }, fn) silently disables while measured enabled (vitest truthy-checks options); (b) a disabled suite (describe.skip/describe.todo/{skip:true}) with a NEW title never propagates disabling to nested registrations — wrapping pre-existing tests emits zero signal while vitest skips every wrapped test (baseline-known-title control fires, which is what makes the new-title variant the clean bypass); (c) optionsDisable omits the fails key — it('a', { fails: true }, fn) pins the OPPOSITE behaviour with zero signal while the .fails chain member IS classified (vitest 3.2.7 applies fails from options: a FALSE assertion reports 'Tests 1 passed', exit 0); (d) the guard signal counts only bare return; — return undefined;/return void 0;/return null; bypass by spelling (round-23 entrance, re-probed); (e) the literal skipIf/runIf evaluators miss foldable constant expressions — skipIf(!!true)/runIf(!true) measure enabled while vitest evaluates eagerly and skips; (f) isBodySkip's argument rule is the inverse of the runner's — ctx.skip(null)/ctx.skip(0)/ctx.skip(undefined) skip at runtime (runner: condition === false is the ONLY non-skip) but measure enabled; (g) memberName resolves an element-access collector index only for string/template literals — it['ski' + 'p']('a') and const S='skip'; it[S]('a') skip at runtime, measure enabled; (h) conditional body-skips false-charge — if (cond) ctx.skip(); measures disabled while the twin skip(cond, reason) measures enabled, charging the repo's environment-guard idiom; (i) assertions in variable-initializer position are never counted — const res = await request(app)…expect(202): a repo sweep finds 137 such assertions in 11 files (top: multi-workspace-sessions.test.ts +73, workspace-remember.test.ts +33) invisible to the counter, so deleting one measures all-zero; (j) the guard walk's conditional-return false-charge twin (probed on local-diff.integration.test.ts:667) — the counting of bare returns, conditional or not, is the documented contract (fixture 'early-return' pins it); what stands open is the control-flow-context absence producing the charge asymmetry with the skip spellings.
Witness:
Round-24 probes at HEAD (unmodified counter vs vitest 3.2.7):
(a) {skip:'…'} enabled:1; vitest: skipped
(b) describe.skip(new title) wrap => {assertions:0,guards:0,enabled:0,newlyDisabled:[]}; vitest: legacy a: skipped/todo
(c) {fails:true} enabled:1/disabled:[] vs it.fails disabled:["test:a"]; vitest: Tests 1 passed on a FALSE assertion, exit 0
(d) 'return undefined;' guards:0 vs bare 'return;' guards:1
(e) skipIf(!!true)/runIf(!true)/skipIf(!false) enabled:1; vitest: skipped
(f) ctx.skip(null|0|undefined) enabled:1; vitest: skipped; ctx.skip(false): passed
(g) it['ski'+'p'] / it[S] enabled:1; vitest: skipped; fold-fix flips to disabled
(h) 'if (cond) ctx.skip()' => disabled:['test:a'] => gate charges; twin spelling enabled
(i) real file: assertions:162 with and without the matcher removed; measure all-zero; patched counter flips to assertions:-1;
sweep: 137 missed assertions in 11 of 2409 files
The class fix round 21 asked for stands: replace name/shape attribution with runner-grounded semantics (the runner's own collected-test enumeration, or per-construct modeling of vitest's truthiness/focus/fails/options and nesting). Per-entrance spelling fixes will reopen on the next construct.
Condition-valued guards (skipIf(cond), skip(cond, reason)) must stay uncharged per the NOT MEASURED contract (run-autofix-review-verification.sh:1085-1090); assertion-carrying returns must keep counting the assertion and not a guard; the CLI toEqual output-shape pin at scripts/tests/qwen-autofix-workflow.test.js:25819 forbids new output fields. Fix witness: pin each demonstrated entrance; removing any arm of the fix must redden exactly its own row while the skipif-guard/ctx-skip-conditional acceptance fixtures stay green.
中文说明
仍然存在(第 24 轮):类级问题——count() 仅凭链根标识符名与语法形状判定注册、禁用与守卫归属,不做绑定分析、不建模控制流。第 21/22/23 轮全部入口原样成立(代码自第 21 轮逐字节一致)。该检测面无法逐入口补完——每修一种写法都会被下一种 JS 构造重新打开;本 PR 第 12–23 轮的历史就是证据。本轮在本提交上验证十个新入口(均经未改动计数器与 vitest 3.2.7 对照探针):(a) optionsDisable 只解码字面量 true,{ skip: '理由' } 静默禁用却被测为启用;(b) 新标题的禁用套件不向嵌套注册传播——包装既有测试零信号而 vitest 全部跳过;(c) optionsDisable 缺 fails 键——{ fails: true } 零信号钉住相反行为(链成员 .fails 却被分类);(d) 守卫只数裸 return;,return undefined;/void 0/null 按拼写绕过;(e) skipIf/runIf 字面量判定不折叠常量表达式(!!true/!true);(f) isBodySkip 参数规则与运行器相反——ctx.skip(null|0|undefined) 运行时跳过却测为启用;(g) 元素访问收集器索引只解字符串/模板字面量——it['ski'+'p']、it[S] 运行时跳过却测为启用;(h) 条件式体内跳过误计费——if (cond) ctx.skip(); 测为禁用而孪生拼写 skip(cond, reason) 测为启用;(i) 变量初始化位置的断言从不计数——全仓扫描 11 个文件 137 处(multi-workspace-sessions.test.ts +73、workspace-remember.test.ts +33 领先),删除任一处测得全零;(j) 守卫遍历对条件式裸 return 的误计费孪生形态(local-diff.integration.test.ts:667 实测)——数裸 return 本身是文档化契约('early-return' 夹具钉住),未闭合的是控制流上下文缺失造成的计费不对称。
证人证据:见英文版探针输出(本提交执行)。
第 21 轮请求的类级修复仍然成立:以运行器背书的语义取代按名/形状归属。逐入口修补会被下一种构造重新打开。条件式守卫按 NOT MEASURED 契约保持不计费;携带断言的 return 须继续计断言;:25819 的 CLI 输出形状钉子禁止新增字段。修复见证:为每个入口补钉子;移除任一部分须恰好使自己的行变红。
— qwen3.8-max via Qwen Code /review (v0.23.0)
| # The branch holds no blob: main added the file and it lands; or the | ||
| # branch deleted it earlier and main's edit is a modify/delete conflict | ||
| # resolved for the branch's deletion. | ||
| [[ -z "${base}" ]] && printf '%s\n' "${p2}" |
There was a problem hiding this comment.
[Critical] R21-3: [certifies-falsely] [new-surface] Still stands (round 24): a round's own deletion of a pre-existing test file is silently exempted when a main-derived merge follows the deletion and main's side still holds the file. At the merge commit the first-parent diff is quiet (absent vs absent) but the second-parent diff differs, so an event is recorded; weaken_auto_blob hits this p1-empty branch with a non-empty base and returns nothing; the event becomes {before: null, after: null}; baselinePresent flips false; the shell takes the "not the round's to weaken" branch and accepts — although the file was held by the pre-round baseline and is absent at the tip, against this section's stated "never on a whole-file deletion" contract.
Round 24 probed BOTH directions at HEAD (code unchanged). Erase direction (E2E through the real gate; fixture: round git rms pkg/a.test.ts, main edits it, merge resolved for the deletion): UNMODIFIED status=0, rejection='', outcome=fixed — deletion exempted. Mirror direction (new this round, same root): when the p1-empty arm has NO base either, it returns the branch's OWN blob, so an identical-content event {before: own, after: own} flips baselinePresent TRUE — a round that creates pkg/foo.test.ts, merges main that never held it, then deletes it, is charged 'test file deleted' for deleting its own file.
Witness:
Round-24 probes (real gate / unmodified measure()):
ERASE: PROBE-N7-CONTROL status=1 rejection '…test file deleted…'
PROBE-N7 status=0 rejection='' outcome=fixed <- deletion exempted
MIRROR: measure({tip:null, pre:null, events:[{before:own, after:own}]}) -> baselinePresent:true
contract says false; identity-guarded flip -> false, main-adds stays true, main-deletes stays false
Suggested fix — skip recording events where both before and after are empty ([[ -n "${before}" || -n "${after}" ]] || continue); equivalently let measure() keep the prior baselinePresent when an event's before/after contents are identical.
The fix must not break count-test-surface.mjs:384's role as the only mechanism by which a MAIN deletion exempts a round (pinned by the merge-delete-freight fixture) — the skip may fire only when before is also empty. Fix witness: a WEAKEN_FIXTURES entry (round git rms F, mainMoves edits F, merge resolved for the deletion) asserting rejection with signal 'test file deleted', plus a measure case asserting {before:one, after:one} keeps baselinePresent:false; removing either guard must turn its case red.
中文说明
仍然存在(第 24 轮):轮次自己删除既有测试文件、随后发生 main 派生合并且 main 侧仍持有该文件时,删除被静默豁免:合并提交第一父 diff 静默而第二父有差异,事件被记录;weaken_auto_blob 走到 p1 为空、base 非空分支返回空;事件成为 {before: null, after: null};baselinePresent 翻为 false;shell 走「非本轮可弱化」分支接受——尽管文件轮前存在、tip 缺失,违反「整文件删除绝不放行」契约。
第 24 轮在本提交上验证了两个方向(代码未变):擦除方向端到端复测——原代码放行(outcome=fixed);镜像方向(本轮新证,同一根因)——当 p1 为空且 base 也为空时该臂返回分支自己的 blob,内容相同的事件 {before: own, after: own} 把 baselinePresent 翻成 true:轮次自建 pkg/foo.test.ts、合并从未持有它的 main、再删除它,会被计费 'test file deleted'——为删除自己的文件被误拒。
证人证据:见英文版探针(真实门 + 未改动 measure();两方向均复现,修复翻转成立)。
建议修复:跳过 before/after 均为空的事件(代码块见英文);等价地让 measure() 在事件前后内容相同时保持原 baselinePresent。约束:不得破坏 count-test-surface.mjs:384 作为 MAIN 删除豁免唯一机制的角色(merge-delete-freight 夹具钉住),跳过只能在 before 同为空时触发。修复见证:补夹具(轮次 git rm F、mainMoves 编辑 F、合并按删除解决)断言以 'test file deleted' 拒绝,并补 measure 用例断言 {before:one, after:one} 保持 baselinePresent:false;移除任一守卫须变红。
— qwen3.8-max via Qwen Code /review (v0.23.0)
| git merge-file -p --ours "${p1}" "${base}" "${p2}" > "${out}" 2> /dev/null || true | ||
| [[ -s "${out}" ]] || cp "${p1}" "${out}" |
There was a problem hiding this comment.
[Critical] R21-4: [certifies-falsely] [new-surface] Still stands (round 24): [[ -s "${out}" ]] conflates a legitimate EMPTY auto-merge result with a merge-file hard failure. When main empties a test file (keeps the file, deletes all content) and the round merges main, git merge-file -p --ours succeeds with rc=0 and 0-byte output; the -s test then copies the branch's pre-merge content over main's side, main's delta measures 0, and the round is rejected retryably with 'net N assertion(s) removed' — a false charge for main's edit, contradicting the comment above's claim that only a hard failure leaves the output empty.
Round 24: code byte-identical since round 23; the conflation was re-derived again at HEAD by the state-propagation sweep (merge-file rc=0 with empty output is a clean success, the -s fallback still fires).
Witness:
Round-22 measurement at this commit (re-confirmed round 24):
git merge-file -p --ours with main's side truncated -> rc=0, out size 0 (legitimate empty success)
[[ -s ]] false -> cp p1 fallback fires
Round-21 end-to-end fixture (main empties F, round merges main unchanged):
PR: CAND3_STATUS=1, rejection '- pkg/a.test.ts — net 2 assertion(s) removed'
Fix flip (fall back to p1 only on nonzero exit): CAND3_STATUS=0, outcome=fixed
Constraint: NUL-byte (binary) input -> rc=255 with empty output, so the fallback must survive for nonzero exit
| git merge-file -p --ours "${p1}" "${base}" "${p2}" > "${out}" 2> /dev/null || true | |
| [[ -s "${out}" ]] || cp "${p1}" "${out}" | |
| weaken_merge_rc=0 | |
| git merge-file -p --ours "${p1}" "${base}" "${p2}" > "${out}" 2> /dev/null || weaken_merge_rc=$? | |
| if (( weaken_merge_rc != 0 )) && [[ ! -s "${out}" ]]; then | |
| cp "${p1}" "${out}" | |
| fi |
The branch-side fallback must stay for nonzero exit (binary content exits 255 with empty stdout; a --ours conflict merge exits 0 with output — exit-status-keyed fallback is safe), per the comment at run-autofix-review-verification.sh:1181. Fix witness: a WEAKEN_FIXTURES entry where main empties F and the round merges main unchanged, asserting the gate accepts; restoring [[ -s ]] || cp must turn it red.
中文说明
仍然存在(第 24 轮):[[ -s "${out}" ]] 把「合法的空自动合并结果」与「merge-file 硬失败」混为一谈。main 清空测试文件(保留文件、删除全部内容)且轮次合并 main 时,git merge-file -p --ours 以 rc=0 输出 0 字节成功;-s 判定把分支侧内容覆盖到 main 侧,main 增量测得 0,轮次被以「net N assertion(s) removed」可重试地拒绝——为 main 的编辑向轮次误计费,与上方注释「只有硬失败才留下空输出」矛盾。
第 24 轮:代码自第 23 轮逐字节一致;状态传播专项在本提交再次推导确认(rc=0 空输出是干净成功,-s 兜底仍会触发)。
证人证据:见英文版(本提交实测 + 第 21 轮端到端夹具:原代码误拒;仅非零退出兜底后接受;二进制输入 rc=255 约束)。
建议修复(建议块见英文):捕获退出码,仅在失败时回退分支侧。约束:非零退出必须保留分支侧兜底(二进制内容 255 空输出;--ours 冲突合并 0 有输出),见 :1181 注释。修复见证:补夹具(main 清空 F、轮次原样合并 main)断言门接受;还原 [[ -s ]] || cp 须变红。
— qwen3.8-max via Qwen Code /review (v0.23.0)
| done < <(git diff --name-only -z --no-renames --diff-filter=D "origin/${BRANCH}" "${BRANCH}" \ | ||
| -- "${WEAKEN_PATHSPEC[@]}" 2> /dev/null) |
There was a problem hiding this comment.
[Critical] R21-9: [certifies-falsely] [new-surface] Still stands (round 24): the UNAVAILABLE arm's deletion enumeration filters on --diff-filter=D only, which misses typechange (T): replacing an existing test file with a symlink removes its entire declared surface without producing a deletion entry, so the arm fails open for exactly the class its contract says it never fails open for ('Fails OPEN on the measured … and never on a whole-file deletion' — the same pre-round→tip pair proves a typechange). The measured branch catches this shape (per-commit enumeration has no filter), so the hole exists only in this degraded arm — which the suite itself exercises via noCounter: true. Round 24 re-check: the enumeration still filters --diff-filter=D only.
NEW THIS ROUND at this same enumeration (round-24 finding R24-2, folded into this thread because both mechanisms live in these two lines): the loop also SWALLOWS its producer's exit status — done < <(git diff …) makes the status invisible. When the enumeration's git call fails (the diff's own gitDiffFails shadow-git stages exactly this; transient lock contention or an unresolvable origin/${BRANCH} produce it in production), the loop sees EOF, WEAKENED_PATHS stays empty, and whole-file deletions ship silently — violating the same contract and the rule weaken_add_diff's own comment encodes ('The producer's status is read, not swallowed behind a process substitution').
Witness:
R21-9 (round-21 probes, code unchanged):
git-level: --diff-filter=D -> (empty); --diff-filter=DT -> pkg/a.test.ts
E2E via noCounter:true symlink fixture: UNMODIFIED status=0 (accepted, file now a symlink);
WITH DT: status=1 'test file deleted'
R24-2 (round-24 E2E through the repo's own runGate harness at HEAD):
INTACT runGate({weaken:'delete', gitDiffFails:true}):
{status:0, rejection:'', unavailableLogged:true, deletionSignal:false}
control (producer intact, noCounter:true): status=1 'test file deleted'
FIXED (enumerate to temp file, read producer status, retryable reject_fix on failure):
{status:1, rejection:'test-weakening gate could not enumerate whole-file deletions; refusing to certify their absence'}
| done < <(git diff --name-only -z --no-renames --diff-filter=D "origin/${BRANCH}" "${BRANCH}" \ | |
| -- "${WEAKEN_PATHSPEC[@]}" 2> /dev/null) | |
| done < <(git diff --name-only -z --no-renames --diff-filter=DT "origin/${BRANCH}" "${BRANCH}" \ | |
| -- "${WEAKEN_PATHSPEC[@]}" 2> /dev/null) |
(the ```suggestion block addresses R21-9's filter; R24-2 additionally needs the enumeration redirected to a temp file whose producer status is read, failing closed with a retryable reject_fix on failure.) The arm must stay deletion-judging only (header contract at run-autofix-review-verification.sh:1093-1096) and bash-3.2 clean (the meta-test regex-scans the gate section). Fix witness: extend 'judges deletions even when the measurement is unavailable' (~line 25506) with (1) a symlink-swap fixture under noCounter: true asserting status 1 'test file deleted', and (2) runGate({weaken:'delete', gitDiffFails:true}) expecting status 1 / retryable=true; today both pass green at status 0. Note: the existing expect(refused.status).toBe(0) assertion at scripts/tests/qwen-autofix-workflow.test.js:25528 pins today's fail-open behaviour and must move with the R24-2 fix.
中文说明
仍然存在(第 24 轮):UNAVAILABLE 分支的删除枚举只按 --diff-filter=D 过滤,漏掉类型变更(T):把既有测试文件替换为符号链接会移除其全部声明表面却不产生删除条目,该分支恰好在契约声称「绝不放行」的类别上放行。测量分支能抓住该形态,漏洞只在这个退化分支。第 24 轮复查:枚举仍只用 --diff-filter=D。
本轮在同一枚举上的新发现(R24-2,因两个机制同在这两行而并入本线程):该循环还吞掉了生产者的退出状态——done < <(git diff …) 使状态不可见。当枚举的 git 调用失败(diff 自带的 gitDiffFails 影子 git 正是这种形态;生产中瞬态锁竞争或不可解析的 origin/${BRANCH} 会触发),循环看到 EOF,WEAKENED_PATHS 保持为空,整文件删除静默放行——违反同一契约,也违反 weaken_add_diff 注释自己写下的规则(「生产者状态被读取,而不是被进程替换吞掉」)。
证人证据:见英文版(R21-9:DT 过滤翻转成立;R24-2:runGate 端到端——原代码 status=0 放行,修复后 status=1 可重试拒绝,控制组可判别)。
建议修复:--diff-filter=DT(建议块)+ R24-2 需把枚举重定向到临时文件并读取生产者状态,失败时以可重试 reject_fix 失败关闭。约束:该分支仅做删除判定(:1093-1096 契约)且保持 bash-3.2 干净。修复见证:把「无测量时也判删除」测试扩展两个用例——符号链接替换(noCounter: true)与 runGate({weaken:'delete', gitDiffFails:true}),均断言 status 1;今天两者以 status 0 绿着。注意 :25528 的 expect(refused.status).toBe(0) 钉住的是今天的放行行为,须随 R24-2 修复移动。
— qwen3.8-max via Qwen Code /review (v0.23.0)
| # from the head prepare classified: the premise is unknown, never green. | ||
| PUSH_PRE="${CHECK_STATE:-none}" | ||
| [[ "${PUSH_RACE_MERGED}" == 'true' ]] && PUSH_PRE='none' | ||
| echo "<!-- autofix-push round=${NEXT_ROUND} head=${PUSHED_HEAD} pre=${PUSH_PRE} key=${WINDOW:-none} -->" |
There was a problem hiding this comment.
[Critical] R22-1: [certifies-falsely] [new-surface] Still stands (round 24): the new autofix-push seed marker rides solely in the report body: when the push landed but the report post fails, no fallback posts it (POST_HANDOFF excludes OUTCOME=fixed, yml:6092), so the regression af-155 exists to charge can never be charged. Round N: prepare classifies the head green, OUTCOME=fixed, the push loop lands H_N; gh pr comment fails all 3 bounded attempts (the API-brownout case the retry loop exists for) and the script exits 1 at the REPORT_POSTED guard. The failure-report step runs (failure()), but POST_HANDOFF stays false for a fixed outcome — round N posts nothing; the marker exists only in the never-posted report.md. If the pushed fix turned checks red, no future prepare can charge it (charge requires the newest persisted push marker's head= to equal the live head), and once any later bot push lands the matching marker is no longer sort_by(.at) | last anyway — the window closes permanently. The sibling autofix-regression marker has cross-round redundancy; the push marker has none. Round 24 re-check: the sole emitter is still inside report.md; yml:6092 still excludes OUTCOME=fixed; no fallback post was added.
Witness:
Traced hop by hop at the reviewed commit (unchanged):
emitter: autofix-push-and-report.sh:655 (sole, inside report.md; grep: no other emitter)
exit: '[[ "${REPORT_POSTED}" == true ]] || exit 1' after 3 attempts
yml:6092 guard excludes the fixed-outcome post-failure shape
charge conjunct requires LP_HEAD==CHECKED_OUT_HEAD — no marker, no charge
witness: not run — the envelope needs a live GitHub API; control flow is deterministic once it occurs
Suggested fix — make the seed record survive independently of the full report post: right after the push loop (before the post attempts), write round_pushed=true and pushed_head=$(git rev-parse HEAD) to $GITHUB_OUTPUT; when REPORT_POSTED is false and ROUND_PUSHED true, best-effort post a minimal marker-only comment — or extend the failure-report path to OUTCOME=fixed with steps.push_report failed, writing the push marker there.
The workflow file is within a few KB of the repo's 470,000-byte gate and GitHub stops starting runs past 512,000 silently (script lines 5-9) — prefer a script-side fallback; the POST_HANDOFF fixed/noop exclusion (yml:6092) is deliberate per af-138 — a fix routed through it must not start posting handoff comments for successfully-reported fixed/noop rounds. Fix witness: an af-155 suite case 'push succeeds, report post fails → push marker still reaches the PR and the next prepare charges that round when checks are red'; it must go red if the fallback post is removed.
中文说明
仍然存在(第 24 轮):新的 autofix-push 种子标记只搭载在报告正文里:推送成功但报告发布失败时没有任何兜底发布它(POST_HANDOFF 排除 OUTCOME=fixed,yml:6092),于是 af-155 要计费的回归永远无法被计费。gh pr comment 三次有界重试全部失败(重试循环正是为 API 故障窗口而设)后脚本在 REPORT_POSTED 守卫处退出 1;失败报告步骤虽运行,但 fixed 结果下 POST_HANDOFF 保持 false——轮次什么都不发,标记只存在于从未发出的 report.md。若推送的修复把检查弄红,未来任何 prepare 都无法计费(计费要求最新持久化 push 标记的 head= 等于活 head);一旦后续任何 bot 推送落地,本可匹配的标记也不再是 sort_by(.at) | last,窗口永久关闭。兄弟 autofix-regression 标记有跨轮冗余;push 标记没有。第 24 轮复查:唯一发射器仍在 report.md 内;yml:6092 仍排除 OUTCOME=fixed;仍无兜底发布。
证人证据:见英文版逐跳追踪(未运行——需要真实 GitHub API;控制流一旦进入即为确定)。
建议修复:推送循环之后、发布尝试之前,把 round_pushed=true 与 pushed_head 写入 $GITHUB_OUTPUT;REPORT_POSTED 为 false 且 ROUND_PUSHED 为 true 时尽力发布仅含标记的最小评论——或把失败报告路径扩展到 steps.push_report 失败时的 OUTCOME=fixed。约束:workflow 距 470,000 字节门只有几 KB(512,000 时 GitHub 静默停止启动运行)——优先脚本侧兜底;POST_HANDOFF 的 fixed/noop 排除是 af-138 刻意设计,不得经由它为成功报告的 fixed/noop 轮次发 handoff。修复见证:af-155 补用例「推送成功、报告发布失败 → push 标记仍到达 PR,检查转红时下一轮计费该轮」;移除兜底须变红。
— qwen3.8-max via Qwen Code /review (v0.23.0)
| while IFS= read -r -d '' f; do | ||
| [[ -n "${f}" ]] || continue | ||
| if git cat-file -e "${PR_BASE}:${f}" 2> /dev/null && | ||
| ! git cat-file -e "origin/main:${f}" 2> /dev/null; then |
There was a problem hiding this comment.
[Critical] R22-2: [certifies-falsely] [new-surface] Still stands (round 24): the UNAVAILABLE arm's freight exemption is unsound: 'present at PR_BASE, gone from origin/main' proves main deleted the file at some point after the merge base — not that this round adopted that deletion via a merge — so a round that directly deletes a stale pre-existing test file is silently exempted whenever main happens to have deleted it earlier, contradicting the gate's own 'never fails open on a whole-file deletion' invariant (lines 1093-1096) and the arm's own fail-closed fallback when PR_BASE degrades. The measured arm charges this exact sequence (pre ≠ null → baselinePresent=true → 'test file deleted'), so the degraded arm fails open on precisely the signal the header claims the pre-round→tip pair proves without the walk. Round 24 re-check: the exemption at lines 1297-1299 is unchanged.
Witness:
Driven through the real gate script via the PR's own runGate harness
(onMain seeds pkg/a.test.ts; main deletes it after the fork; the round
deletes it directly with git rm, never merging main):
F8 MEASURED status=1 rejection='**round weakened pre-existing tests...' - pkg/a.test.ts — test file deleted
F8 UNAVAILABLE status=0 rejection='' unavailableNotice=true
Flip check: exemption disabled (mutant 'if false; then continue; fi' over lines 1297-1299):
F8 UNAVAILABLE status=1 with the deletion charged under the UNAVAILABLE notice
Suggested fix — in the UNAVAILABLE arm, surface every pre-round→tip deletion: drop the exemption, or apply it only when freight is provable; freight is ackable via test-weakening.json with evidence, matching the arm's own stated preference when the merge base is unresolvable ('every deletion is then surfaced rather than dropped').
The header invariant 'Fails OPEN on the measured signals … and never on a whole-file deletion' (lines 1093-1096) and the arm's fail-closed degradation comment (lines 1289-1291) require the fix to stay fail-closed under uncertainty. Fix witness: a UNAVAILABLE-arm case (counter absent) where main deletes a test file after the merge base, the branch stays behind, and the round deletes it directly; it must assert the path is charged and go red while the exemption drops it.
中文说明
仍然存在(第 24 轮):UNAVAILABLE 分支的 freight 豁免不健全:「在 PR_BASE 存在、在 origin/main 消失」只能证明 main 在合并基之后的某个时刻删除了该文件——不能证明本轮通过合并采纳了那次删除——于是只要 main 恰好早先删过,轮次直接删除一个陈旧的既有测试文件就会被静默豁免,与门自身「整文件删除绝不放行」的不变量(1093-1096 行)以及该分支在 PR_BASE 退化时的失败关闭兜底相矛盾。测量分支对完全相同的序列计费,退化分支恰在抬头声称「无需走查即可证明」的信号上放行。第 24 轮复查:1297-1299 行豁免未变。
证人证据:见英文版 runGate 真实门输出(测量分支计费、UNAVAILABLE 分支放行;禁用豁免的变异体使 UNAVAILABLE 分支也计费,探针翻转)。
建议修复:在 UNAVAILABLE 分支列出全部轮前→tip 删除——去掉豁免,或仅在可证明 freight 时应用;freight 可经 test-weakening.json 附证据 ack。约束:修复必须保持不确定时失败关闭(抬头不变量 1093-1096 与退化注释 1289-1291)。修复见证:补一个 UNAVAILABLE 用例(计数器缺失):main 在合并基后删除测试文件、分支落后、轮次直接删除;断言该路径被计费,且在豁免仍在时变红。
— qwen3.8-max via Qwen Code /review (v0.23.0)
| elif any($c[]; (.conclusion // .state // "") | ||
| | IN("FAILURE", "FAILED", "ERROR", "TIMED_OUT", "ACTION_REQUIRED")) then "red" | ||
| elif any($c[]; ((.status // "") | IN("QUEUED", "IN_PROGRESS", "WAITING", "PENDING")) | ||
| or ((.conclusion // .state // "") | IN("PENDING", "", "EXPECTED", "STALE", "STARTUP_FAILURE"))) then "pending" |
There was a problem hiding this comment.
[Critical] R22-3: [certifies-falsely] [new-surface] Still stands (round 24): the head-state classifier maps a CANCELLED check to green — the charge-enabling state — because CANCELLED matches neither the red list (line 4768) nor the pending list (lines 4769-4770) and falls to else "green". Three lines above, the diff's own invariant reads: 'A check with no verdict (EXPECTED: a required context that never reported; STALE; a run that never started) is pending, never green.' A cancelled run produced no pass/fail verdict on the head — it is the runtime sibling of STARTUP_FAILURE, which the classifier itself parks in pending. The pin at scripts/tests/qwen-autofix-workflow.test.js:26037 (.toBe('green') for CANCELLED) justifies the mapping as consistency with the scan's N_RED_NOW filter — but not-red does not entail green; the classifier could agree with N_RED_NOW and still say pending.
Round 24 drove the verbatim HEAD jq: one CANCELLED check (alone or beside green siblings) → green; corroborations at HEAD — N_FAILED_CHECKS (yml:3366) counts CANCELLED as a failure, so the loop stays engaged on exactly such heads, while N_RED_NOW (yml:3378) does not; the charge block requires "${LP_PRE:-}" == 'green' (yml:4787), so classifying CANCELLED as pending removes the false-charge path while keeping the not-red property the test comment argues for. Concrete timeline: cancelled required check → prepare stamps pre=green on the no-verdict head → bot pushes → fresh CI surfaces the pre-existing failure the cancelled run masked → next prepare's four facts hold and charge the prior round for a red it did not author, and the brake counts it as non-progress.
Witness:
Round-24 verbatim-HEAD jq drive:
one CANCELLED check only -> green CANCELLED + green -> green
all green -> green one FAILURE -> red
one IN_PROGRESS -> pending one STALE -> pending empty rollup -> none
N_FAILED_CHECKS conclusions: FAILURE FAILED ERROR TIMED_OUT ACTION_REQUIRED CANCELLED
N_RED_NOW conclusions: FAILURE FAILED ERROR TIMED_OUT ACTION_REQUIRED
Round-23 two-round execution of the extracted block:
BASELINE: check_state=green off the CANCELLED premise -> round 3 CHARGED
FIX (CANCELLED added to the pending arm): check_state=pending -> no charge
CONTROL (observing round sees CANCELLED, not FAILURE): still green — rationale preserved
| or ((.conclusion // .state // "") | IN("PENDING", "", "EXPECTED", "STALE", "STARTUP_FAILURE"))) then "pending" | |
| or ((.conclusion // .state // "") | IN("PENDING", "", "EXPECTED", "STALE", "STARTUP_FAILURE", "CANCELLED"))) then "pending" |
and flip the pinned test row at scripts/tests/qwen-autofix-workflow.test.js:26037 to .toBe('pending').
The classifier's red list must stay CANCELLED-free to match N_RED_NOW (yml:3378, pinned by scripts/tests/qwen-autofix-workflow.test.js:1696-1701) — the fix must go through the pending arm, not the red arm. Fix witness: the flipped test row goes red if CANCELLED is removed from the pending arm; the existing pushMarker({ pre: 'pending' }) charge row already pins that a pending premise blocks the charge.
中文说明
仍然存在(第 24 轮):head 状态分类器把 CANCELLED 检查映射为 green——计费启用态——因为 CANCELLED 既不在红列表(4768 行)也不在 pending 列表(4769-4770 行),落入 else "green"。三行之上,diff 自己的不变量写着:「没有判决的检查……是 pending,绝不是 green」。被取消的运行未在 head 上产生任何通过/失败判决——它是 STARTUP_FAILURE 的运行时兄弟,而分类器自己把 STARTUP_FAILURE 放进 pending。:26037 的钉子以「与 N_RED_NOW 一致」为由——但「非红」不蕴含「绿」。
第 24 轮逐字驱动 HEAD 的 jq:单个 CANCELLED(或与 green 并存)→ green;HEAD 佐证——N_FAILED_CHECKS(yml:3366)把 CANCELLED 计为失败(循环恰在这类 head 上保持介入)而 N_RED_NOW 不计;计费块要求 LP_PRE == 'green'(yml:4787),故归入 pending 即移除误计费路径且保留「非红」性质。具体时间线:必需检查被取消 → prepare 在无判决头上盖 pre=green → bot 推送 → 新 CI 暴露被掩盖的既有失败 → 下一轮 prepare 四事实成立计费上一轮,刹车记为非进展。
证人证据:见英文版(逐字 jq 驱动 + 第 23 轮提取块双轮执行:基线计费、修复后不计费、对照保留设计理由)。
建议修复(建议块见英文):把 "CANCELLED" 加入 pending 分支,并把 :26037 测试行翻转为 .toBe('pending')。约束:红列表必须保持不含 CANCELLED 以匹配 N_RED_NOW(yml:3378,test:1696-1701 钉住)——修复必须走 pending 分支。修复见证:翻转后的测试行在移除 CANCELLED 时变红;既有 pushMarker({ pre: 'pending' }) 用例已钉住 pending 前提阻断计费。
— qwen3.8-max via Qwen Code /review (v0.23.0)
| while IFS= read -r c; do | ||
| [[ -n "${c}" ]] || continue | ||
| if ! git rev-parse -q --verify "${c}^" > /dev/null 2>&1; then | ||
| WEAKEN_MEASURED='false' |
There was a problem hiding this comment.
[Critical] R23-1: [certifies-falsely] [new-surface] Still stands (round 24): the history walk self-aborts into the UNAVAILABLE fallback on a parentless (root) commit in the round range — a history shape the reviewed branch itself authors — which waives the gate's entire non-deletion evidence requirement. A merge-base-preserving orphan shape reaches it: an orphan side branch carrying the weakened test becomes the FIRST PARENT of the round tip (git checkout --orphan + git merge --allow-unrelated-histories); the pre-round head stays reachable through the merge's second parent, so no force push is needed to publish. The abort is not forced by unmeasurability: a root commit is trivially own (no event subtraction ever references c^ for kind own), so the abort converts a branch-controlled state into a blanket exemption. The FULL orphan rewrite does not reach this gate — the pre-existing unguarded DELETED_TESTS diff at line 993 dies first, fail-closed; the reachable shape is the merge-base-preserving one. Round 24 re-check: the abort is unchanged.
Witness:
Round-23 E2E through the repo's own runGate harness (code unchanged at round-24 HEAD):
PROBE-ORPHAN-MERGE status=0 | outputs: outcome=fixed
PROBE-ORPHAN-MERGE unavailable-line: test-weakening measurement UNAVAILABLE
this round — only whole-file deletions are judged
PROBE-ORPHAN-MERGE rejection-len=0 <- a round that cut pkg/a.test.ts from
2 assertions to 1, accepted with no test-weakening.json
FIX flip (root classified 'own', enumerated against the empty tree):
PROBE-ORPHAN-MERGE status=1 | outcome=failed retryable=true
rejection: '…- pkg/a.test.ts — net 1 assertion(s) removed'
Full-orphan control: status=128 — crash in pre-existing unguarded code at
line 993 (fail-closed, not a waiver)
Suggested fix — do not abort on a root commit: classify it own, and in the candidate loop substitute a diff against the empty tree (git diff "$(git hash-object -t tree /dev/null)" "${c}") when git rev-parse -q --verify "${c}^" fails, so the round's full surface is still measured and charged. Keep the UNAVAILABLE fail-open for genuine producer failures (git errors in weaken_add_diff/weaken_measure).
The fix must keep the deletion arm intact and must not turn genuine walk/counter failures into silent passes (the gate header's fail-open contract, run-autofix-review-verification.sh:1093-1096), and the replacement must stay bash-3.2 clean (the meta-test regex-scans the gate section). Fix witness: a round range containing a parentless commit that removes assertions from a pre-existing test file must reject without test-weakening.json; restoring the abort makes it go red (today it logs UNAVAILABLE and accepts).
中文说明
仍然存在(第 24 轮):历史走查在轮次范围内遇到无父提交(根提交)时自行中止并退入 UNAVAILABLE 兜底——被审分支自己就能构造的历史形态——豁免了门的全部非删除证据要求。可达形态是保留合并基的孤儿结构:携带弱化测试的孤儿侧分支成为轮次 tip 的第一父(git checkout --orphan + git merge --allow-unrelated-histories),轮前 head 经第二父仍可达,无需强推。中止并非不可测量所致:根提交天然属于 own(own 类事件差分从不引用 c^),中止把分支可控状态变成整体豁免。完全孤儿重写到不了此门——993 行既有的无守卫 DELETED_TESTS diff 先以失败关闭方式崩溃;可达的是保留合并基的形态。第 24 轮复查:中止未变。
证人证据:见英文版(runGate 真实门端到端:原代码以 outcome=fixed 接受删断言的孤儿合并轮;把根提交归类为 own 并对空树枚举后翻转为拒绝;完全孤儿对照在 993 行以 128 失败关闭)。
建议修复:不要在根提交处中止——归类为 own,候选循环在 git rev-parse -q --verify "${c}^" 失败时改用对空树的 diff(git hash-object -t tree /dev/null);真正的生产者失败保持 UNAVAILABLE 放行。约束:保住删除分支、不得把真实失败变成静默放行(:1093-1096 契约),替换实现保持 bash-3.2 干净。修复见证:含无父提交的轮次范围删除既有测试断言,须无需 test-weakening.json 即拒绝;还原中止须变红。
— qwen3.8-max via Qwen Code /review (v0.23.0)
Two holes let an autofix round ship a new problem for free. Neither is
reachable by the deterministic gate as it stands, and both are cheap to close
with signals the loop already has.
1. A regression cost the loop nothing
The consecutive-failure brake counts rounds that pushed nothing. A round that
pushed a fix and turned CI red therefore counted as a success: it reset the
counter, and the red it created came back as the next round's input, paid for
out of the round budget. A PR could alternate regress → repair indefinitely
while every brake read it as converging.
grep -rE 'regress|introduced'overthe workflow, the gate script and the SKILL hit only prose about writing
regression tests — the system had no notion of a regression at all.
The gate cannot close this on its own, and says so: it runs build, typecheck,
lint and
--changedtests for the touched workspaces, and defers fullregression to the PR's own CI. Everything the gate does run is already
charged — a check that fails on the round tree and passes at
origin/<branch>is a rejection, and a rejected round pushes nothing and feeds the brake. What
was left uncharged is exactly the post-push signal, whose verdict does not
exist until long after the job ended.
So the accounting is deferred rather than waited on, and it is measured:
autofix-push round= head= pre= key=—pre=isthe head state the round started from,
head=is the sha it pushed;is exactly that sha (nothing else moved the branch),
pre=green(fully green—
pendingandnoneare not green, so a check still running at push timecan never be charged), the window key matches, and the head is red now;
autofix-regression round= key=into whicheverreport it posts — pushed, no-op or failure — so the record survives it;
own observation, which is not in the fetched comments yet. Without that the
newest regressing push escapes by exactly one round — the round that matters.
Attribution is conservative on every axis that could charge the loop for
someone else's red: a moved head (human push, base update), a re-arm, a
pending check at push time, a cancelled run, and the loop's own lanes all drop
the charge. A genuinely flaky check is the residual, and the consequence is
bounded on purpose — one regression only declines to reset a counter that
needs five consecutive non-progress rounds to trip, and a clean push clears it.
The terminal headline changes from "consecutive rounds that pushed nothing" to
"consecutive rounds without progress (… or a push that left the checks red)",
so a maintainer does not read "pushed nothing" on a PR whose every round pushed.
2. Weakening an existing test was invisible
Relaxing an existing assertion is the cheapest way for a fix to reach green
while the behaviour it broke goes unpinned, and it was structurally invisible
to every check in the gate: build/typecheck/lint never read assertions, the
package tests run the weakened file, and the bite check reads only the tests a
round adds — never the ones it edits away. The SKILL already required
content evidence for deleting or weakening a test; nothing enforced it, and the
existing shrink advisory renders only after the round is accepted.
The gate now measures each pre-existing test file's declared test surface
with the TypeScript compiler's parser (
.github/scripts/count-test-surface.mjs,staged from the trusted base like the gate itself):
expect(x).toBe(1),expect.soft(x).toBe(1),await expect(p).rejects.toThrow(),expect.unreachable(...),assert(...),assert.equal(...), supertest.expect(...)) — a bareexpect(x)or aproperty-accessed matcher counts nothing;
it/test/describe/suiteregistration with its enabled state, bytitle —
.skip/.todo/.fails, computed and escaped spellings,xit,literal
skipIf(true)/runIf(false), a literal{ skip: true }, abody-level unconditional
skip()/ctx.skip();returns in a test body's own control flow ahead of its assertions.Each file's round delta is
tip − pre-round − main's contribution, wheremain's contribution across a merge is git's own auto-merge of main's side
(
git merge-file --ours) and across a fast-forwarded main commit is that commititself — so a weakening measures the same before, during or after a merge, an
assertion moved within a file nets zero, and main's delta neither charges nor
shields. Signals, one per file: the file was deleted (held by the baseline,
absent at the tip); net assertions removed; a baseline-enabled registration now
disabled; net enabled registrations removed; early returns added. Files are
selected by name (
*.test.*,*.spec.*,test_*.py,tests/*.rs,*_test.rs,*_tests.rs; snapshots excluded); non-JS shapes are judged by thedeletion arm alone.
Each such file must be named in
<workdir>/test-weakening.json(
[{"path": …, "reason": …}], reason ≥ 40 characters) or the round is rejected— retryably, so the same-run repair pass can restore the coverage or record the
evidence. The gate judges that the claim exists, not that it is right: no
semantic oracle is available here, and turning a silent edit into an explicit
attributable claim is the point. The reasons ride into the round report,
rendered from the measured set only and neutralized like every other
agent-authored excerpt.
Not measured, by design (stated in the gate header): reachability (dead
code, a condition false in CI, a helper never called), condition-valued guards
(
.skipIf(cond),skip(cond, reason)— the repo's environment-guard idiom),options carried by reference, count-preserving matcher relaxations,
it.eachtable rows, and a Rust
#[cfg(test)]module inside a production file. Thoseare runtime facts; the package test run and the bite check are the runner-backed
instruments, and the advisory certifies only the declared surface.
Verification
npx vitest run --config ./scripts/tests/vitest.config.ts scripts/tests/qwen-autofix-workflow.test.js— 277 passed;
scripts/tests/workflow-size.test.js— 198 passed (Linux/bash 5;the accepted-round gate tests carry the
hasBashMapfilehost gate).counter staged under
RUNNER_TEMPas the workflow stages it; the counter hasan in-process table of 15 shapes plus a CLI contract test and direct
measurealgebra cases.expect()counted, guard position dropped, options object ignored, disabled arm
dropped, merge treated as own,
--theirsbaseline, deletion ignoring thebaseline, fast-forward events skipped, second-parent enumeration dropped,
UNAVAILABLE freight exemption dropped, enumeration failure swallowed, render
bar dropped, dispatch exclusion removed, rollup binding removed, plus the
pre=green/ head-equality / window / regression-branch mutants of theoriginal set.
check-workflow-size.sh,prettier --check,eslint --max-warnings 0,shellcheck -xon both changed scripts,actionlinton the workflow: clean..size-baselinerecords the exact byte count ofqwen-autofix.yml.Deliberately not in this change
push, so a loop that regresses and repairs forever without ever failing is
still bounded only by the round cap. A dedicated cap is a separate design
decision (threshold, message, re-arm interaction); this change lays down the
measurement it would need.
new from main, so there is no pre-existing PR test to weaken.
中文说明
有两个口子,让 autofix 的一轮修复可以零代价地引入新问题。两者都不在当前确定性门的覆盖范围内,而且都可以用循环已有的信号低成本关掉。
1. 引入回归不花任何代价
连续失败熔断统计的是「没有推送任何内容的轮次」。因此一轮推送了修复、却把 CI 弄红的round 会被算作成功:它重置了计数器,而它制造的红会作为下一轮的输入回来,由轮次预算买单。于是一个 PR 可以在「引入回归 → 修回归」之间无限交替,而所有刹车都读作正在收敛。对 workflow、门脚本与 SKILL 做
grep -rE 'regress|introduced',只命中关于编写回归测试的散文——系统里根本没有「回归」这个概念。门自身关不掉这个口子,而且它自己写明了:它只跑 build、typecheck、lint 以及所触及 workspace 的
--changed测试,把全量回归交给 PR 自己的 CI。门确实跑到的部分早已计费——在本轮树上失败、在origin/<branch>上通过的检查就是一次拒绝,被拒的轮次不推送任何内容,本就喂给熔断。真正没被计费的,恰恰是 push 之后的信号,而它的结论要等到 job 结束很久之后才存在。所以这里采用延迟记账而不是等待,并且全部基于测量:
autofix-push round= head= pre= key=——pre=是该轮起始时的 head 状态,head=是它推送出去的 sha;pre=green(完全绿——pending与none都不算绿,因此 push 时仍在跑的检查永远不会被记账)、窗口 key 一致、且现在是红的;autofix-regression round= key=写进它自己发的任意一种报告(推送 / 无改动 / 失败),这样记录不会随该轮消失;在所有可能把别人的红算到循环头上的维度,归因都取保守方向:head 移动过(人工推送、base 更新)、发生过 re-arm、push 时检查仍在 pending、被 cancel、以及循环自己的 lane,都会放弃记账。真正的 flaky 检查是残余风险,而后果是刻意设计成有界的——一次回归只是不重置一个需要连续 5 轮无进展才触发的计数器,且一次干净的推送就会清零。
终止文案从「连续 N 轮未能推送任何内容」改为「连续 N 轮没有进展(……或推送后检查转红)」,避免维护者在一个每轮都在推送的 PR 上读到「未能推送任何内容」。
2. 弱化既有测试是不可见的
放宽一条既有断言是让修复变绿的最廉价手段,而它破坏的行为就此失去钉住;这对门里的每一项检查都是结构性不可见的:build/typecheck/lint 不读断言,包测试跑的是已被弱化的文件,bite 检查只读本轮新增的测试,从不读被改掉的。SKILL 早已要求删除或弱化测试必须给出内容证据,但没有任何强制,既有的 shrink advisory 也只在轮次已被接受之后才渲染。
门现在用 TypeScript 编译器的解析器(
.github/scripts/count-test-surface.mjs,与门一样从可信基线暂存)计量每个既有测试文件的声明的测试面:expect(x).toBe(1)、expect.soft(x).toBe(1)、await expect(p).rejects.toThrow()、expect.unreachable(...)、assert(...)、assert.equal(...)、supertest 的.expect(...))——裸expect(x)或只属性访问的 matcher 计零;it/test/describe/suite注册项及其启用状态,按标题——.skip/.todo/.fails、计算属性与转义拼写、xit、字面量skipIf(true)/runIf(false)、字面量{ skip: true }、测试体内无条件的skip()/ctx.skip();return。每个文件的本轮增量是
tip − 轮前 − main 的贡献:跨合并时 main 的贡献取 git 自己对 main 侧的自动合并结果(git merge-file --ours),跨 fast-forward 搭车的 main 提交时取该提交本身——因此同一处弱化无论发生在合并前、中、后计量相同,文件内移动断言净为零,main 的增量既不计费也不遮蔽。信号每文件一条:文件被删除(基线持有、tip 缺失);净删除断言;基线启用的注册项被禁用;净删除启用的注册项;新增提前 return。文件按名称选择(*.test.*、*.spec.*、test_*.py、tests/*.rs、*_test.rs、*_tests.rs;排除快照);非 JS 形状只由删除臂判定。每个此类文件必须记入
<workdir>/test-weakening.json([{"path": …, "reason": …}],reason ≥ 40 字符),否则该轮被拒绝——可重试,同一次运行的修复趟可以恢复覆盖或记录证据。门判定的是该声明存在,而不是它正确:这里没有语义预言机,把静默改动变成显式、可归属的声明才是重点。理由会进入轮次报告,只从被计量的集合渲染,并与其它 agent 撰写的摘录一样做中和处理。按设计不计量(写在门头注释里):可达性(死代码、CI 下为假的条件、从未调用的辅助函数)、条件式守卫(
.skipIf(cond)、skip(cond, reason)——本仓库的环境守卫惯用法)、按引用传递的 options、保持计数的 matcher 放宽、it.each表格行、生产文件里内联的 Rust#[cfg(test)]模块。这些是运行时事实;包测试与 bite 检查是有运行器背书的仪器,advisory 只认证声明的测试面。验证
npx vitest run --config ./scripts/tests/vitest.config.ts scripts/tests/qwen-autofix-workflow.test.js——277 通过;scripts/tests/workflow-size.test.js——198 通过(Linux/bash 5;接受轮的门用例带hasBashMapfile主机门)。RUNNER_TEMP下;计数器另有进程内 15 种形状的用例表、CLI 契约用例与measure代数用例。expect()计数、guard 位置去掉、options 对象忽略、disabled 臂去掉、合并当本轮、--theirs基线、删除不看基线、跳过 ff 事件、去掉第二父枚举、去掉 UNAVAILABLE 的 freight 豁免、吞掉枚举失败、去掉渲染门槛、去掉 dispatch 排除、去掉 rollup 绑定,以及原有的pre=green/head 相等/窗口/回归分支变异体。check-workflow-size.sh、prettier --check、eslint --max-warnings 0、两个脚本的shellcheck -x、workflow 的actionlint:全部干净。.size-baseline记录qwen-autofix.yml的精确字节数。本次刻意不做的部分