docs(autofix): design runner-level isolation for PAT-bearing steps - #9525
Conversation
PR #9214 tried to close issue #9089 from inside the job by running the verification gate in an ephemeral container. Eleven review rounds took its diff from 329 to 2,700 lines while the per-round Critical count rose 8 -> 9 -> 15 -> 19 -> 29; rounds 10 and 11 changed no code and round 11 still produced 14 new Criticals on byte-identical files, six of them reporting that a site the diff added lacked the discipline the same diff installed elsewhere. That PR is frozen; its findings are inventoried in issue #9524. Two of its results shape this design. The container cannot help, because the gate's root of trust sits outside it: the digest chain is rooted in step outputs that the runner parses last-wins at step end, from a file under attacker-writable RUNNER_TEMP, with no sweep before the staging step. And the wrapper could never have run post-merge at all, which the pre-merge skip path hid from CI. The design moves the boundary from inside a step to between jobs: an execute job that runs branch code and holds no credentials, and a publish job that runs no branch code, re-derives its own decision from the patch instead of trusting the gate verdict, and holds the PAT. Agent process trees are killed by cgroup lineage rather than by an env marker, and the in-step enumeration machinery the split makes redundant is deleted rather than kept.
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
Thanks for the design doc — and for the honest post-mortem of the #9214 attempt behind it. One gate item before content review: the PR body doesn't follow the PR template. It uses freeform headings (## Why, ## What this proposes, ## Notes) where the template asks for ## What this PR does, ## Why it's needed, ## Reviewer Test Plan, ## Risk & Scope, and ## Linked Issues.
@wenshao could you restructure the body onto the template headings? The substance is already all there, so this is a heading remap: "What this proposes" → ## What this PR does, "Why" → ## Why it's needed, "Notes" → ## Risk & Scope, plus a docs-applicable ## Reviewer Test Plan (How to verify: review the design against #9089 / #9524; Evidence and Tested on: N/A) and lifting "Refs #9089, #9524. Supersedes the approach in #9214" into ## Linked Issues. A @qwen-code /triage re-run will pick it up from there.
中文说明
感谢这份设计文档,也感谢背后对 #9214 尝试的坦诚复盘。进入内容审查前有一个门槛项:PR 正文没有遵循 PR 模板。正文使用了自由标题(## Why、## What this proposes、## Notes),而模板要求的是 ## What this PR does、## Why it's needed、## Reviewer Test Plan、## Risk & Scope、## Linked Issues。
@wenshao 能否把正文调整到模板标题下?内容已经齐全,主要是标题对应调整:"What this proposes" → ## What this PR does,"Why" → ## Why it's needed,"Notes" → ## Risk & Scope,加上文档类 PR 适用的 ## Reviewer Test Plan(How to verify:对照 #9089 / #9524 审阅设计;Evidence 与 Tested on:N/A),并把 "Refs #9089, #9524. Supersedes the approach in #9214" 放进 ## Linked Issues。之后用 @qwen-code /triage 重新触发即可继续。
— Qwen Code · qwen3.8-max
|
📄 Docs-only change — the automatic review ran at
Reviewed head: 中文说明📄 纯文档变更 —— 自动评审以 |
|
@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 冲突,直到移除标签或达到轮次上限。移除 |
|
🤖 AutoFix updated a stale base — the fix did not pass verification, but this PR was behind What I found before stopping: BlockerThe round's only finding is triage review rv:4978214393 (CHANGES_REQUESTED, stage 1a): the PR body must move from freeform headings ( What needs a human (mechanical remap, per the review)The PR author or a maintainer remaps the body headings: "What this proposes" -> 中文说明🤖 AutoFix 更新了一个过期的 base —— 修复未通过验证,但本 PR 落后于 停止前我了解到的情况: 阻塞点本轮唯一的发现是 triage 评审 rv:4978214393(CHANGES_REQUESTED,第 1a 阶段):PR 正文必须从自由标题( 需要人工完成的事项(机械的标题重映射,按评审所述)由 PR 作者或维护者重映射正文标题:"What this proposes" -> Run log: https://github.com/QwenLM/qwen-code/actions/runs/32355912104 🧠 Handled by Qwen Code · model/模型 |
|
🤖 Reviewed the latest feedback — no changes needed. Why, point by point: · 已审阅最新反馈——无需改动。逐点说明原因如下: Feedback triaged this round — 1 item, 0 addressable by a code change. [rv:4978214393] (review, Disposition: real and left open — actionable only by the PR author or a maintainer on GitHub, not by this agent.
No code changes were made and nothing was committed. No repository-defined verification command was run this round because nothing in the repository changed; per the workflow-prepared check data, all CI checks on the current head ( What unblocks this PR (per the triage review — a heading remap; the substance is already all there): the PR author or a maintainer edits the PR body so that "What this proposes" → 中文说明本轮分诊的反馈 —— 共 1 条,其中 0 条可通过代码改动解决。 [rv:4978214393](评审, 处置:真实且保持未解决 —— 只能由 PR 作者或维护者在 GitHub 上处理,本 agent 无法执行。
本轮未做任何代码改动,也没有提交任何内容。由于仓库内没有任何变更,本轮未运行仓库定义的任何验证命令;根据工作流准备的检查数据,当前 head( 解除本 PR 阻塞的操作(按 triage 评审所述 —— 标题重映射;内容已齐备):由 PR 作者或维护者编辑 PR 正文,将 "What this proposes" → Base-conflict check · 基分支冲突检查: no conflict with main. · 与 main 无冲突。 🧠 Handled by Qwen Code · model/模型 |
|
@qwen-code /triage |
|
Sandboxed verification: This PR changes documentation/assets only — there is no code to execute, so a sandboxed verification has nothing to verify. 中文 — 判定:
|
|
Triage re-run completed without a new review.
The stage comments above were updated with the latest result. View workflow run. 上方各阶段评论已更新为最新结果。查看工作流运行。 |
|
🤖 Reviewed the latest feedback — no changes needed. Why, point by point: · 已审阅最新反馈——无需改动。逐点说明原因如下: 🤖 Reviewed the latest feedback — no changes needed. Feedback triaged this round:
Why nothing else:
中文说明🤖 已审阅最新反馈——无需改动。 本轮分诊的反馈:
其余说明:
Base-conflict check · 基分支冲突检查: no conflict with main. · 与 main 无冲突。 🧠 Handled by Qwen Code · model/模型 |
|
@qwen-code /triage |
|
Sandboxed verification: This PR changes documentation/assets only — there is no code to execute, so a sandboxed verification has nothing to verify. 中文 — 判定:
|
|
Triage re-run completed without a new review.
The stage comments above were updated with the latest result. View workflow run. 上方各阶段评论已更新为最新结果。查看工作流运行。 |
|
🤖 Reviewed the latest feedback — no changes needed. Why, point by point: · 已审阅最新反馈——无需改动。逐点说明原因如下: No action this round (PR #9525)Feedback triageThe prepared feedback list contains a single item newer than the last
A cross-check of the raw comment feed found no filtered-out actionable item: DecisionNo code or documentation change is warranted. The PR (a single new design doc 中文说明本轮无需处理(PR #9525)反馈分类准备好的反馈列表中,自上次评估(2026-08-21T02:01:34Z)之后只有一条内容:
对原始评论数据做了交叉核对,确认没有被过滤掉的可处理项:截止时间之后唯一的维护者活动是一条 决定没有需要修改代码或文档的理由。本 PR(仅在 Base-conflict check · 基分支冲突检查: no conflict with main. · 与 main 无冲突。 🧠 Handled by Qwen Code · model/模型 |
|
@qwen-code /triage |
|
Sandboxed verification: This PR changes documentation/assets only — there is no code to execute, so a sandboxed verification has nothing to verify. 中文 — 判定:
|
|
Triage re-run completed without a new review.
The stage comments above were updated with the latest result. View workflow run. 上方各阶段评论已更新为最新结果。查看工作流运行。 |
|
🤖 Reviewed the latest feedback — no changes needed. Why, point by point: · 已审阅最新反馈——无需改动。逐点说明原因如下: Autofix review round: no action neededTriage of feedback newer than the last evaluation (2026-08-21T02:38:27Z) found nothing actionable this round:
No commits were made this round; the PR head is unchanged. 中文说明Autofix 审查轮次:无需处理对上次评估(2026-08-21T02:38:27Z)之后的新反馈进行分类后,本轮没有可处理的事项:
本轮未提交任何 commit;PR 的 head 保持不变。 Base-conflict check · 基分支冲突检查: no conflict with main. · 与 main 无冲突。 🧠 Handled by Qwen Code · model/模型 |
doudouOUC
left a comment
There was a problem hiding this comment.
Reviewed the design doc at HEAD 35cfd3eb. On content, this is a strong design and I'm approving it — with one non-content gate item noted below.
Content — LGTM. The doc is well-reasoned and honest:
- The #9214 post-mortem earns its conclusions rather than asserting them: non-convergence (329→2,700 lines, Critical count 8→9→15→19→29, rounds 10–11 changing no code yet still generating findings) and, decisively, R11-8 — the gate's root of trust (
steps.stage.outputs.*parsed at step end from attacker-writable$RUNNER_TEMP) sits outside any container you could wrap the executor in. That correctly kills the in-step approach. - The core principle — a job that ran branch code must neither hold the PAT nor influence the inputs of the job that does — is the right boundary, and moving it between jobs (execute → artifact → publish) is sound:
$GITHUB_ENV/$GITHUB_OUTPUT/$RUNNER_TEMP/live processes don't cross a job boundary. - Section B is the load-bearing insight and it's handled honestly: the execute job is assumed compromised, so its verdict is untrusted by construction and no signature fixes that — the publish job re-derives from checks that need no branch execution (patch applies to recorded base, footprint allowlist, workflow/lockfile refusal, growth budget, Conventional Commit,
--force-with-lease), with build/test explicitly downgraded to advisory. The doc states that "verified" genuinely weakens, rather than hiding it. - Section E (delete what A–D make redundant) is the right instinct — a guard kept "just in case" is exactly what regenerated #9214's findings. The phased rollout (kill switch → move publish to hosted → cgroup lineage kill → ephemeral registration → delete machinery) sequences the highest-value, lowest-infra parts first, and the open questions (org
--ephemeralpolicy, hosted-runner egress for the PAT job,issue-autofix's differing publish-side checks) are the right ones to leave open.
Two small content notes, neither blocking:
- The publish-side footprint/growth checks (B2, B4) are the same class this pipeline has struggled to make forgery-proof elsewhere; worth stating in the eventual implementation that they run only on the patch text on the trusted side, never re-reading a value the execute job wrote.
issue-autofixis correctly flagged as out of scope here — just make sure the follow-up lands before E deletes shared machinery it still relies on.
Gate item (non-content): the automated triage still has CHANGES_REQUESTED open only because the PR body uses freeform headings (## Why / ## What this proposes / ## Notes) instead of the template's (## What this PR does, ## Why it's needed, ## Reviewer Test Plan, ## Risk & Scope, ## Linked Issues). The substance is all present — it's a heading remap. Restructuring the body and re-running @qwen-code /triage should clear that gate; my content approval above stands independently of it.
|
Thanks — both content notes are now in the doc (
Also cross-referenced #9556 in the header — it is the same question on the review side, and anyone picking up either should see the other. The gate item is fixed too: the body is now on the template headings, with a docs-applicable Reviewer Test Plan and the refs lifted into Linked Issues. Re-running triage next. |
|
@qwen-code /triage |
|
Correction to my reply above: this PR merged (
Sorry for the noise — the claim was accurate about the branch and wrong about |
…9652) Review feedback on QwenLM#9525 arrived after that PR was merged, so the notes it raised never made it into the doc. Both are things an implementer could get wrong from the text as it stands. Say what makes the re-derived checks sound in their new position: they are computed from the patch text on the trusted side and may never re-read a value the execute job wrote — not a line count it reported, not a footprint list it declared, not a base SHA it echoed back. This is the check class that has repeatedly failed to be forgery-proof here, and an implementation that shortcuts to reading the count the gate already computed reintroduces exactly that. Record the sequencing constraint as a constraint: the issue-autofix follow-up has to land before the deletion pass removes machinery it still relies on, which is why that pass is per cluster. Cross-reference QwenLM#9556, which asks the same question on the review side. Refs QwenLM#9089, QwenLM#9525.
… staged file The extraction put the body in a file and then had to defend that file: a staged copy under RUNNER_TEMP, a digest recorded in step output, a type check, bounded reads, and — after three rounds of findings against those — an env -i clean child with a liveness sentinel and a 38-entry pass-through list wrapping the whole verify-and-run sequence. Four review rounds have now been spent on that machinery, and the open findings against it are about the machinery, not about the move: output buffering that costs the live log the inline body had, an env allow-list nothing pins, and pin gaps in the tests that exist only to hold the gate together. Delete the object being guarded instead. The stage step already reads the trusted base before any branch code runs, so it captures the body as text into step output and the step runs those bytes — the delivery the inline block had, and the one upsert-deferred-issue.sh already uses. With no agent-writable copy on disk there is nothing to stage, digest, type-check or re-open, and no check→use window between the steps: R1-2's TOCTOU, R1-13's FIFO double-open, R2-2/R2-5/R2-6/R2-7's single-open pins and R4-1's buffering all describe a mechanism that no longer exists. This is not a security regression. What it drops is protection the inline block never had; what it keeps is the one property that matters — the bytes come from the trusted-base checkout through expression context, not from a path the branch can write. The step shell's inherited environment (R1-1, R1-3) is the status quo this PR preserves rather than the thing an extraction should be fixing: #9525's publish job takes the PAT out of that shell entirely, which is the real close. Kept from the gate work: the round_reported output. A loader plant that kills this shell at execve exits 0 having written nothing, so 'Finalize autofix status comment' can still tell a published round from a no-op — now without a sentinel, because the write is simply last. Also corrected, from the same review: the header no longer claims the block was the largest in the file (it is third, after 'Scan for PRs with new feedback' and 'Prepare branch and feedback'), the size is measured at this branch's merge-base (462,720), the env contract is stated as a rule rather than a list that went stale within one round, and the shellcheck note says which lane and which severity it is clean under. The R10-8 execution-ban negatives now cover the scripts as well as the workflow, and the script's own flag line is pinned as a whole rather than by spelling. Refs #9089, #9525.
…file (QwenLM#9653) * refactor(autofix): move the push-and-report body out of the workflow file qwen-autofix.yml was 462,656 bytes — 90% of GitHub's 500 KB start-runs limit and inside the 470,000-byte gate's warning band. Past that limit GitHub stops starting runs and says nothing: schedule ticks vanish, dispatches sit queued with zero jobs, and only PR-event runs keep working because they resolve an older copy from the PR's branch. The file crossed it once already, on 2026-08-19, and the loop went dark for a day. 'Push and report' was the largest single block in the file at 626 lines of inline shell. Its body moves to .github/scripts/autofix-push-and-report.sh byte-identically — the YAML keeps the step's if: and env:, which is where the questions of when it runs and what reaches it belong. The file drops to 422,447 bytes. Extraction moves the trust problem with it. By the time this step runs the agent and the verification gate have executed branch code on this host, so the workspace copy is branch-controlled. The step runs the trusted-base copy staged before any of that, after proving its digest — recorded in GITHUB_OUTPUT, which a disk write cannot reach — still matches, that the staged path is a regular file, and with both reads bounded so a planted FIFO is a refusal rather than a hang until timeout-minutes. A new contract test pins all of that, including that the workspace copy is never what runs; each of its assertions was checked against a mutant of the line it guards. The census assertions that counted sites across the workflow now count the script too, so an extraction cannot silently drop a site from a count that exists to pin it. The two emit_growth_audit_marker copies are compared dedented, since one is now at column 0 and its twin is still at ten. This is also the step docs/design/autofix-gate-runner-isolation.md moves into its own publish job. Carrying it as a file makes that a small diff, and deletes the staging scaffolding added here: a job that checks out the trusted base and never executes branch code can run the script where it lies. Refs QwenLM#9089. * fix(autofix): close the push-and-report gate's env-import and TOCTOU holes The wrapper verified the staged script in the step's inherited shell: a $GITHUB_ENV-planted PATH or BASH_FUNC_* import could swap the gate's own words, and the staged path was opened three times, so a watcher could swap the file between the digest check and the execution read. Run the whole verify-and-run sequence in one env -i clean child that opens the staged path once, verifies the digest of the captured bytes, and executes those same bytes. Also fail staging closed on any cp failure, fix the script's env contract, and pin the new guards. * fix(autofix): close the push-and-report gate's silent no-op hole Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(autofix): pin gate pass-through, refusal order, and continuation bans (QwenLM#9653) Address review suggestions on the push-and-report extraction: - R2-3: pin the gate child's env -i pass-through list as a sorted allow-list equality check (R9-10 precedent), so a dropped variable fails loudly instead of expanding empty. - R4-4: pin the sentinel refusal BEFORE the round_reported write — shape-only pins let the ordering rot (probe-verified mutant). - R4-6: make the execution bans continuation-tolerant; bash joins `\<newline>` before tokenizing, so line-continued spellings evaded the `[^\n|]*` middle (probe: old regex misses, new catches). - R1-6: re-scope the paired R10-8 upsert negatives to both halves, matching the positive censuses. - R3-2: correct the stale workflow-size figure (462,656 -> 463,004 bytes at the post-merge base; 90%/98% still hold). - R1-5/R1-15/R1-11: correct three disproved claims in the trust-model doc (block rank, GITHUB_OUTPUT qualifier, which call sites hold the single-open shape). * refactor(autofix): deliver the push-and-report body as content, not a staged file The extraction put the body in a file and then had to defend that file: a staged copy under RUNNER_TEMP, a digest recorded in step output, a type check, bounded reads, and — after three rounds of findings against those — an env -i clean child with a liveness sentinel and a 38-entry pass-through list wrapping the whole verify-and-run sequence. Four review rounds have now been spent on that machinery, and the open findings against it are about the machinery, not about the move: output buffering that costs the live log the inline body had, an env allow-list nothing pins, and pin gaps in the tests that exist only to hold the gate together. Delete the object being guarded instead. The stage step already reads the trusted base before any branch code runs, so it captures the body as text into step output and the step runs those bytes — the delivery the inline block had, and the one upsert-deferred-issue.sh already uses. With no agent-writable copy on disk there is nothing to stage, digest, type-check or re-open, and no check→use window between the steps: R1-2's TOCTOU, R1-13's FIFO double-open, R2-2/R2-5/R2-6/R2-7's single-open pins and R4-1's buffering all describe a mechanism that no longer exists. This is not a security regression. What it drops is protection the inline block never had; what it keeps is the one property that matters — the bytes come from the trusted-base checkout through expression context, not from a path the branch can write. The step shell's inherited environment (R1-1, R1-3) is the status quo this PR preserves rather than the thing an extraction should be fixing: QwenLM#9525's publish job takes the PAT out of that shell entirely, which is the real close. Kept from the gate work: the round_reported output. A loader plant that kills this shell at execve exits 0 having written nothing, so 'Finalize autofix status comment' can still tell a published round from a no-op — now without a sentinel, because the write is simply last. Also corrected, from the same review: the header no longer claims the block was the largest in the file (it is third, after 'Scan for PRs with new feedback' and 'Prepare branch and feedback'), the size is measured at this branch's merge-base (462,720), the env contract is stated as a rule rather than a list that went stale within one round, and the shellcheck note says which lane and which severity it is clean under. The R10-8 execution-ban negatives now cover the scripts as well as the workflow, and the script's own flag line is pinned as a whole rather than by spelling. Refs QwenLM#9089, QwenLM#9525. --------- Co-authored-by: qwen-code-dev-bot <qwen-code-dev@service.alibaba.com> Co-authored-by: qwen-code-dev-bot <qwen-code-dev-bot@users.noreply.github.com> Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
|
Released in v0.22.0. |
What this PR does
Adds a design doc for the structural close of #9089: move the autofix trust boundary from inside a step to between jobs.
execute— agent, verification gate, repair. Runs branch code, holds no push credential. Emitsfix.patchplus an advisory verdict as an artifact.publish—needs: execute. Runs no branch code at all (no npm, no build, no test, no repo script, no hooks), re-derives its own decision from the patch text on the trusted side (applies cleanly to the recorded base, footprint allowlist, workflow/lockfile refusal, growth budget, commit-message contract, head-SHA lease) and pushes with the PAT.Plus: kill agent process trees by cgroup lineage instead of an env marker (the source of R8-8/R9-3/R11-10), ephemeral registrations for
execute, running onlypublishon a GitHub-hosted runner so the PAT leaves the shared host entirely, and — explicitly part of the work — deleting the in-step enumeration machinery the split makes redundant. The goal is a smaller trust surface, not another layer.No behaviour change: this PR adds one file under
docs/design/.Why it's needed
PR #9214 attempted to close #9089 from inside the job, by running the verification gate in an ephemeral container. It did not converge: eleven review rounds took its own diff from 329 to 2,700 lines while the per-round Critical count rose 8 → 9 → 15 → 19 → 29. Rounds 10 and 11 changed no code at all, and round 11 still produced 14 new Criticals on byte-identical files — six of them of the form "the site this diff ADDS lacks the discipline this diff installs elsewhere." #9214 is now frozen (draft,
autofix/needs-human); its findings are inventoried in #9524.Two results from that attempt shape this design:
steps.stage.outputs.*; the runner parses a step's$GITHUB_OUTPUTlast-wins at step end, from a file under attacker-writable$RUNNER_TEMP, and no sweep runs before the staging step. A same-uid co-resident can steer both sides of every downstream comparison.-zskip path masks it.The honest trade is stated in the doc: "verified" weakens to "deterministically checked + advisory build/test", because the publish side cannot execute the branch. The comparison is not against a working guarantee — today's verdict is forgeable, and on the #9214 branch it was never produced at all.
Reviewer Test Plan
How to verify
Read the doc against #9089 (the threat model it closes), #9524 (the findings it must account for), and the frozen #9214 (the approach it supersedes). Specifically worth checking: that section B's re-derivation list contains nothing requiring branch execution, and that section E's deletion pass is scoped so
issue-autofixkeeps what it still depends on. Docs-only —prettier --checkis the whole mechanical gate.Evidence (Before & After)
N/A — documentation only, no user-visible or runtime change.
Tested on
Environment (optional)
N/A — no runtime involved;
npx prettier --check docs/design/autofix-gate-runner-isolation.mdpasses.Risk & Scope
issue-autofix, which has the same shape but creates a branch and a PR rather than pushing to an existing head, so its publish-side checks differ and are not designed here. Two open questions are left open rather than assumed — whether the ECS pool permits--ephemeralregistrations, and whether a GitHub-hosted runner is acceptable for the PAT-bearingpublishjob under the current egress policy.Linked Issues
Refs #9089 — the structural close this designs.
Refs #9524 — the findings inventory it must account for.
Refs #9556 — the same question on the review side.
Supersedes the approach in #9214.
中文说明
本 PR 做了什么
为 #9089 的结构性收口新增一份设计文档:把 autofix 的信任边界从"步骤内部"上移到"job 之间"。
execute——agent、验证门、修复。执行分支代码,不持有任何推送凭据,只产出fix.patch与仅供参考的 verdict 作为产物。publish——needs: execute,完全不执行分支代码(无 npm、无构建、无测试、无仓库脚本、无 git hook),在可信侧基于补丁文本自行推导判定(能否干净应用于记录的基线、足迹白名单、拒绝 workflow 与锁文件、增长预算、提交信息规范、head SHA 租约),然后用 PAT 推送。此外:用 cgroup 血缘取代 env 标记来清杀 agent 进程树(R8-8/R9-3/R11-10 的来源);为
execute启用一次性注册;只把publish放到 GitHub 托管 runner,使 PAT 彻底离开共享主机;并且明确把删除因拆分而冗余的步骤内枚举机制纳入本项工作——目标是更小的信任面,而不是再加一层。无行为变更:本 PR 只新增
docs/design/下的一个文件。为什么需要
PR #9214 尝试在 job 内部关闭 #9089——把验证门放进一次性容器里运行。该方案没有收敛:11 轮评审把它自身的 diff 从 329 行推到 2700 行,每轮 Critical 数量为 8 → 9 → 15 → 19 → 29。第 10、11 轮一行代码都没改,第 11 轮仍在逐字节相同的文件上产生 14 个新 Critical,其中 6 条的形态是"这个 diff 新加的地方,缺少这个 diff 在别处安装的纪律"。#9214 现已冻结(draft +
autofix/needs-human),其发现清单见 #9524。该尝试的两个结论决定了本设计:
steps.stage.outputs.*;runner 在步骤结束时以 last-wins 方式解析位于可写$RUNNER_TEMP下的$GITHUB_OUTPUT,且暂存步骤之前没有任何清扫。同 uid 的同驻进程可以同时操控下游每一次比较的两侧。-z跳过路径把它挡住了。文档中如实写明了代价:"已验证"会弱化为"确定性检查 + 仅供参考的构建/测试",因为发布侧不能执行分支代码。但对比基准并不是"原本可用的保证":现有 verdict 本就可伪造,而在 #9214 分支上它根本没被产生过。
评审验证计划
如何验证
对照 #9089(它要关闭的威胁模型)、#9524(它必须交代的发现清单)与已冻结的 #9214(它取代的方案)阅读本文档。特别值得核对两点:B 节的重新推导清单中不含任何需要执行分支代码的项;E 节的删除范围有没有把
issue-autofix仍依赖的东西一并删掉。纯文档改动,机械门只有prettier --check。证据(改动前后)
N/A——纯文档,无用户可见变化,也无运行时变化。
测试平台
环境(可选)
N/A——不涉及运行时;
npx prettier --check docs/design/autofix-gate-runner-isolation.md通过。风险与范围
issue-autofix形态相同但它是新建分支并开 PR,发布侧判定不同,本文未涵盖。两个未决问题如实留白而非假定——ECS 池是否允许--ephemeral注册,以及当前出网策略下携带 PAT 的publish能否放在 GitHub 托管 runner 上。关联 Issue
Refs #9089——本文所设计的结构性收口。
Refs #9524——它必须交代的发现清单。
Refs #9556——评审侧的同一问题。
取代 #9214 的方案。