-
Notifications
You must be signed in to change notification settings - Fork 3k
fix(review): keep host-trusted state out of the container's writable surface #9983
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
ed2ef50
4ae341e
9213610
895462e
3d33de5
a5fbb49
b3ff0b1
226c1fb
e89cdd0
36fde42
78bb556
548887a
ae424f1
9b89f6a
1cba501
7455996
a94be7b
049d6bb
3f10c3f
29b6aae
6346aea
8ac40a1
4fba588
5b0b78c
5b8744c
c6a5ac8
8f881f0
ca37195
f4fcaa8
f477327
da52bea
28da51e
ac2e3ee
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -55,12 +55,14 @@ import { dirname, join, resolve } from 'node:path'; | |
| import { writeStdoutLine, writeStderrLine } from '../../utils/stdioHelpers.js'; | ||
| import { baseWorktreePath } from './lib/paths.js'; | ||
| import { | ||
| untrustedGitfile, | ||
| discardWorktree, | ||
| sanitizedGitEnv, | ||
| worktreeCreateFailureDetail, | ||
| type SweepResult, | ||
| } from './lib/worktree.js'; | ||
| import { runBuildTest, type BuildTestReport } from './build-test.js'; | ||
| import { runEpochMs } from './lib/prompt-record.js'; | ||
|
|
||
| export interface BaseTreeReport { | ||
| /** | ||
|
|
@@ -175,11 +177,55 @@ export function runBaseTree(args: BaseTreeArgs): BaseTreeReport { | |
| // rebase between runs — falls through to the rebuild below.) | ||
| const marker = () => join(tree, '.qwen-review-base-ok'); | ||
| const failedMarker = () => join(tree, '.qwen-review-base-failed'); | ||
| // The run's epoch, the fence the deadline stamps and the session ledger | ||
| // already key on. A run captures its plan once, so this is stable for the | ||
| // whole run and different between two. | ||
| const runEpoch = String(runEpochMs(args.plan)); | ||
| try { | ||
| const stamp = readFileSync(marker(), 'utf8').trim().split('\n'); | ||
| if ( | ||
| existsSync(tree) && | ||
| readFileSync(marker(), 'utf8').trim() === baseSha && | ||
| gitOut(tree, 'rev-parse', 'HEAD') === baseSha | ||
| stamp[0] === baseSha && | ||
| // ...and THIS run built it. `cleanStale` never releases `-base`, so an | ||
| // earlier round's tree stands into this one with a whole containerized | ||
| // build/test phase in between — long enough for the reviewed code to drop | ||
| // untracked executables in here, and `dist/cli.js` is what a host-side A/B | ||
| // runs. `--untracked-files=no` below cannot see them, and refusing any | ||
| // untracked file at all would disable every legitimate reuse; the stamp is | ||
| // the provenance that separates the two. | ||
| stamp[1] === runEpoch && | ||
|
Comment on lines
+195
to
+196
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [Critical] R15-4: [certifies-falsely] Still standing — the epoch fence does not close this class: every input the base-tree provenance fence keys on is attacker-writable from the sandbox read-write mount, so the reviewed code can make the pipeline certify an attacker-chosen commit as the A/B base — no marker forge needed. The stamp's epoch is Witness: Fix direction — move the fence's provenance out of the mount, one structural change closing all four entrances: generate a random per-run nonce at plan capture and persist it beside The fix must keep same-run reuse alive and cheap — base-tree.ts:172-174 ("a tree that exists, holds the right commit, and carries the marker a successful build wrote is returned as-is") exists so shard B's opening sweep cannot destroy the tree shard A is mid-A/B in — and must respect base-tree.ts:151-157, where Please add arms to base-tree.test.ts that (a) forge the marker with an epoch computed from the plan file's mtime plus untracked-only planting and assert the report does not contain 'reusing it', (b) rewrite the plan's mergeBaseSha to headSha and rerun, asserting no base is certified at headSha, and (c) plant the failed marker into a mkdir'd base path, asserting the rebuild proceeds — all red at this HEAD — and confirm by mutation that removing the nonce comparison or the pin reds each respective arm again. 中文说明仍然成立——epoch 围栏没有关闭这一类问题:base-tree 来源围栏所依赖的每一个输入都可以被被审代码从沙箱读写挂载内改写,因此被审代码可以让流水线把攻击者选定的提交认证为 A/B 基线——完全不需要伪造 marker。stamp 的 epoch 是 修复方向——把围栏的来源信息移出挂载,一处结构化修改关闭全部四个入口:在 plan 捕获时生成每运行一次的随机 nonce,存放在 约束:修复必须保住同运行复用的存活与廉价——base-tree.ts:172-174(「树存在、提交正确、携带成功构建写下的 marker 就原样返回」)正是为了防止 shard B 的开场清扫摧毁 shard A 正在 A/B 的树——并且必须尊重 base-tree.ts:151-157: 请向 base-tree.test.ts 添加三个手臂:(a) 用从 plan 文件 mtime 计算的 epoch 伪造 marker 并只做未跟踪植入,断言报告不含 'reusing it';(b) 把 plan 的 mergeBaseSha 改写为 headSha 后重跑,断言没有以 headSha 认证的基线;(c) 向 mkdir 出来的基线路径植入 failed marker,断言走重建——三者在本 HEAD 上都应为红——并用变异确认:移除 nonce 比较或钉住值后对应手臂再次变红。 — qwen3.8-max via Qwen Code /review (v0.22.3)
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [Critical] R15-4: [certifies-falsely] [new-surface] Still standing (rounds 15/17, re-derived at this commit): the base-tree reuse fence keys on inputs the sandbox's read-write mount exposes — the epoch is Witness: Fix: move the trust artifacts out of the writable surface — the round-17 thread's direction: a random per-run nonce persisted beside Fix witness: a new base-tree.test.ts case with production geometry (plan INSIDE 中文说明依然成立(第 15/17 轮提出,本轮在该提交上重新推导):base-tree 复用栅栏依赖的输入暴露在沙箱读写挂载之下——epoch 是 修复:把信任凭据移出可写面——第 17 轮讨论的方向:在 修复见证:在 base-tree.test.ts 新增生产几何用例(plan 位于 — qwen3.8-max via Qwen Code /review (v0.22.3)
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Escalated for a maintainer's decision — thread left open. Re-derived at this head and it stands: the reuse fence's epoch is This is not a decline — the finding is verified real. It is left open because the call is a maintainer's, not mine: the author put the same question in round 8 (keep gating entrance by entrance in this pull request, or split the gitfile half out and land the lease migration, which has been stable and Critical-free for many rounds), round 18's own review independently reached The prescribed fix is a per-run random nonce persisted beside Earlier re-posts of this same finding carry the same answer: 中文说明升级给维护者决定 —— 线程保持开放。 在当前 head 上重新推导,仍然成立:复用栅栏的 epoch 是 这不是驳回 —— 该发现已验证为真。保持开放是因为这个判断属于维护者,不属于我:作者在第 8 轮就摆出了同一个问题(是继续在本 PR 里按入口逐个设门,还是把 gitfile 那一半拆出去、先落地已经连续多轮稳定且零 Critical 的 lease 迁移),第 18 轮评审自己也独立走到了 给出的修法是在 同一发现的更早重复发布给出同样的答复:
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [Critical] R15-4: [certifies-falsely] [new-surface] Still standing (rounds 15/17/18; re-derived at the round-19 head — base-tree.ts is byte-identical to the round-18 anchor): the base-tree reuse fence keys on inputs the sandbox's read-write mount exposes — the epoch is Witness: Fix direction (round-17 thread): move the trust artifacts out of the writable surface — a random per-run nonce persisted beside Fix witness owed: a new base-tree.test.ts case with production geometry (plan INSIDE 中文说明依然成立(第 15/17/18 轮提出;在第 19 轮的 head 上重新推导——base-tree.ts 与第 18 轮锚点逐字节一致):base-tree 复用栅栏依赖的输入暴露在沙箱读写挂载之下——epoch 是 证据:本轮在该 head 上重新推导: 修复方向(第 17 轮讨论):把信任凭据移出可写面——在 欠交的修复见证:在 base-tree.test.ts 新增生产几何用例(plan 位于 — qwen3.8-max via Qwen Code /review (v0.22.3)
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [Critical] R15-4: [certifies-falsely] [new-surface] Still standing (rounds 15/17/18/19; re-derived at the round-20 head — base-tree.ts is byte-identical): the base-tree reuse fence keys on inputs the sandbox's read-write mount exposes — the epoch is Witness: Fix direction (carried from the round-17 thread): move the trust artifacts out of the writable surface — a random per-run nonce persisted beside 中文说明依然成立(第 15/17/18/19 轮提出;在第 20 轮的 head 上重新推导——base-tree.ts 逐字节未变):base-tree 复用栅栏依赖的输入暴露在沙箱读写挂载之下——epoch 是 — qwen3.8-max via Qwen Code /review (v0.23.0)
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [Critical] R15-4: [certifies-falsely] [new-surface] Still standing (rounds 15/17/18/19/20; re-derived at the round-21 head): the base-tree reuse fence keys on inputs the sandbox's read-write mount exposes — the epoch is Witness: 中文说明仍然成立(第 15/17/18/19/20 轮;在第 21 轮 head 再次推出):base-tree 复用栅栏依赖的输入暴露在沙箱读写挂载内 —— epoch 是 — qwen3.8-max via Qwen Code /review (v0.23.0)
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [Critical] R15-4: [certifies-falsely] [new-surface] Still standing (rounds 15/17/18/19/20/21; probe-confirmed again at this head): the base-tree reuse fence keys on inputs the sandbox's read-write mount exposes — the epoch is Witness: Fix direction (the open thread's): move the trust anchor out of the writable surface — a random per-run nonce persisted beside Fix constraint: base-tree.ts:167-171 — "without this, shard B's opening sweep destroys the tree shard A is mid-A/B in", so the nonce must be stable within one run; base-tree.ts:155-160 — Fix witness: a new base-tree.test.ts case with production geometry (plan inside 中文说明依然成立(第 15/17/18/19/20/21 轮;本轮在该提交上再次以探针确认):base-tree 复用栅栏依赖的输入暴露在沙箱读写挂载之下——epoch 是 修复方向(开放讨论串中的):把信任凭据移出可写面——在 修复约束:base-tree.ts:167-171——「否则分片 B 的开场清扫会毁掉分片 A 正在 A/B 的树」,因此 nonce 必须在同一轮内保持稳定;base-tree.ts:155-160—— 修复见证:在 base-tree.test.ts 新增生产几何用例(plan 位于 — qwen3.8-max via Qwen Code /review (v0.23.0)
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [Critical] R15-4: [certifies-falsely] [new-surface] Still standing (rounds 15-22; probe-confirmed again at this head) — the base-tree reuse fence keys on inputs the sandbox's read-write mount exposes: the epoch is Witness: Move the trust artifacts out of the writable surface, per the round-17 direction: a random per-run nonce persisted beside The fix must keep same-run reuse alive and cheap — the fast path is the concurrency guard ( 中文说明仍然成立(第 15-22 轮;本轮再次经探针确认)——base-tree 复用栅栏依赖沙箱读写挂载暴露的输入:epoch 是 修复建议:Move the trust artifacts out of the writable surface, per the round-17 direction: a random per-run nonce persisted beside — qwen3.8-max via Qwen Code /review (v0.23.0)
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [Critical] R15-4: [certifies-falsely] [new-surface] Still standing (rounds 15-24; probe re-confirmed at this head) — the reuse fence keys on the plan's mtime, and the plan file lives inside the very directory the sandbox mounts read-write, so the adversary the fence exists to keep out can read the key and forge the stamp. A run that dies abnormally leaves the Witness: Fix: park the run-epoch's source outside the writable mount — e.g. write the plan (or a small epoch sidecar) beside the leases this PR already moved to The fix must keep same-run reuse alive and cheap (the opening sweep must not destroy the tree a sibling shard is mid-A/B in, base-tree.ts's own rationale), and a 中文说明仍然成立(第 15-24 轮;本轮探针再次确认)——复用围栏以 plan 文件的 mtime 为键,而 plan 文件就在沙箱以读写方式挂载的目录里,因此围栏要防的对手可以读到键并伪造戳记。异常退出的运行会留下 证据:探针——原样代码:伪造戳记 → note 含 'reusing it',builds: 1,植入的 dist/cli.js 存活;对照臂(plan mtime +60s、不伪造)→ 重建。 修复:把运行纪元的来源挪出可写挂载——例如把 plan(或一个小的 epoch 旁车文件)写到本 PR 已经迁移的 修复必须保持同一运行内的复用仍然可用且廉价(开场清扫不能毁掉兄弟分片正在 A/B 的树,见 base-tree.ts 自身的理由),且 — qwen3.8-max via Qwen Code /review (v0.23.0)
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [Critical] R15-4: [certifies-falsely] [new-surface] Still standing (rounds 15-25; probe re-confirmed rounds 15-24, mechanism re-verified at this head, independently re-derived this round) — the reuse fence keys on the plan's mtime, and the plan file lives inside the very directory the sandbox mounts read-write, so the adversary the fence exists to keep out can read the key and forge the stamp. A run that dies abnormally leaves the Witness: Fix: park the run-epoch's source outside the writable mount — e.g. write the plan (or a small epoch sidecar) beside the leases this PR already moved to The fix must keep same-run reuse alive and cheap (the opening sweep must not destroy the tree a sibling shard is mid-A/B in, base-tree.ts's own rationale), and a 中文说明仍然成立(第 15-25 轮;第 15-24 轮探针确认,本轮机制复查通过,且本轮独立重新推导)——复用围栏以 plan 文件的 mtime 为键,而 plan 文件就在沙箱以读写方式挂载的目录里,因此围栏要防的对手可以读到键并伪造戳记。异常退出的运行会留下 证据:第 24 轮探针——原样代码:伪造戳记 → note 含 'reusing it',builds: 1,植入的 dist/cli.js 存活;对照臂(plan mtime +60s、不伪造)→ 重建。第 25 轮:机制在本 head 复查确认;本轮 chunk-1 审查员独立重新推导,其验证者以代码追踪确认。 修复:把运行纪元的来源挪出可写挂载——例如把 plan(或一个小的 epoch 旁车文件)写到本 PR 已经迁移的 修复必须保持同一运行内的复用仍然可用且廉价(开场清扫不能毁掉兄弟分片正在 A/B 的树,见 base-tree.ts 自身的理由),且 — qwen3.8-max via Qwen Code /review (v0.23.0)
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [Critical] R15-4: [certifies-falsely] [new-surface] Still standing (rounds 15-26; probe re-confirmed at this head) — the base-tree reuse fence keys on inputs the sandbox's read-write mount exposes: the epoch is Witness: Fix: move the trust artifacts out of the writable surface — a random per-run nonce persisted beside The fix must not violate the escalated thread's own constraints: keep same-run reuse alive and cheap (base-tree.ts:172-174 exists so shard B's opening sweep cannot destroy the tree shard A is mid-A/B in), and respect base-tree.ts:149-157 ( 中文说明依然成立(第 15-26 轮;本轮在该 head 上再次探针确认)——base-tree 复用栅栏依赖的输入暴露在沙箱读写挂载之下:epoch 是 — qwen3.8-max via Qwen Code /review (v0.23.0) |
||
| // The reuse path RETURNS, so the gate below the rebuild never runs for | ||
| // it — and both facts it reuses on come from inside the mount: the | ||
| // marker is a file in the base tree, and `rev-parse HEAD` resolves | ||
| // through that tree's own `.git`. A planted repository answers the right | ||
| // sha for a working tree holding anything at all, and the A/B's BASE | ||
| // side is then the reviewed code's own — every "the base behaves | ||
| // differently" verdict the shard reports would be its author's. | ||
| // | ||
| // Not a refusal: an unusable leftover is what the rebuild exists for. | ||
| // Falling through discards the tree (removing the plant with it) and | ||
| // creates a fresh one through the review worktree's pointer, which the | ||
| // gate before `worktree add` checks. Same shape as `scratch-tree`'s | ||
| // reuse path, for the same reason. | ||
| untrustedGitfile(tree) === null && | ||
|
qwen-code-dev-bot marked this conversation as resolved.
|
||
| gitOut(tree, 'rev-parse', 'HEAD') === baseSha && | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [Critical] R25-1: [certifies-falsely] The epoch fence excludes only trees built by a DIFFERENT run — it is blind to an untracked executable plant dropped into a tree THIS run built, while reviewed code still holds the read-write mount, and the pipeline's own documented flow guarantees that window. Witness: Fix: do not let provenance alone vouch for untracked content — build/hold the base tree outside the directory the sandbox mounts read-write, or record a manifest of the pipeline build's own outputs at marker-write time and compare it at reuse; or at minimum document and pin with a test that the base tree is only ever built after the run's final containerized exposure of reviewed code — and close the post-build exposure that contradicts it. Same-run reuse must stay alive and cheap — the fast path exists so a concurrent shard does not clobber a tree a sibling is mid-A/B in (base-tree.ts's own rationale) — and the dirt check must stay 中文说明epoch 围栏只排除由「不同运行」构建的树——它对「本轮运行自己构建的树」中被植入的未跟踪可执行文件是盲的,而被审查代码此时仍持有读写挂载,且流水线自己的文档化流程保证了这个窗口存在。 证据:第 25 轮探针——对一棵持有植入 dist/cli.js 的 base 形态工作树运行 git status --porcelain --untracked-files=no → 输出:[EMPTY] → CLEAN:植入存活而复用被认证;HEAD 与 baseSha 匹配;含未跟踪的变体只显示 ?? .qwen-review-base-ok,看不到被 gitignore 的植入物。顺序在本 head 追踪确认:agent-briefs 把 base-tree 焊接在 test-delta 之前(经 refuseUnsandboxedPhase 门,容器化经 build-test 的 run,或沙箱关闭时宿主直跑)。对 runBaseTree 本体的 vitest 探针未运行——重建后的审查工作树没有 node_modules(第一轮工作树在运行中途被外部清扫销毁)。 修复:不要让「来源证明」独自为未跟踪内容背书——把 base 树构建/保存在沙箱读写挂载目录之外,或在写 marker 时记录流水线自身构建产物的清单并在复用时比对;或至少用测试文档化并钉住「base 树只在本轮对被审查代码的最后一次容器化暴露之后构建」——并关闭与之矛盾的构建后暴露。 同一运行内的复用必须保持可用且廉价——快速通道的存在是为了让并发分片不毁掉兄弟正在 A/B 的树(base-tree.ts 自己的理由)——且脏检查必须保持 — qwen3.8-max via Qwen Code /review (v0.23.0)
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [Critical] R25-1: [certifies-falsely] Still standing (rounds 25-26; probe re-confirmed at this head) — the epoch fence excludes only trees built by a DIFFERENT run: it is blind to an untracked executable plant dropped into a tree THIS run built, while reviewed code still holds the read-write mount, and the pipeline's own documented flow guarantees that window (the sandboxed build/test phase overlaps the other agents, and the base tree "is a direct child of the directory the sandbox mounts read-write"). A sandboxed PR-side command writes Witness: Fix: fence reuse on the tree's untracked surface, not just its run identity — at marker write, record HOST-SIDE (outside the mount; the marker itself is attacker-writable per R15-4's class) a digest of the untracked path set the build legitimately left; at reuse, require the current untracked set to be a subset of the recorded one, rebuilding otherwise. base-tree.ts fast-path comment: " 中文说明依然成立(第 25-26 轮;本轮探针再确认)——epoch 栅栏只排除由另一次运行构建的树:对被审代码仍持有读写挂载时放进本次运行所建树中的未跟踪可执行植入物,它是盲的,而流水线自己文档化的流程保证了这个窗口(沙箱化构建/测试阶段与其他代理重叠,且 base 树「是沙箱以读写挂载的目录的直接子目录」)。沙箱化的 PR 侧命令在标记写入后写入 — qwen3.8-max via Qwen Code /review (v0.23.0) |
||
| // ...and its CONTENTS must still be the base's. `rev-parse HEAD` does not | ||
| // move when working files change, and this tree is a direct child of the | ||
| // directory the sandbox mounts read-write — which is why the pointer gate | ||
| // above speaks at all. So the reviewed PR's own build, which runs before | ||
| // the verifier shards get here, can overwrite the base checkout's tracked | ||
| // sources with a plain copy: every condition above still passes, and the | ||
| // A/B then compares the PR against a copy of itself. A test the PR breaks | ||
| // fails identically on both sides, `test-delta` classifies the regression | ||
| // as pre-existing, and a real finding against the PR is suppressed. | ||
| // | ||
| // `--untracked-files=no`: the pipeline's own build leaves `node_modules/` | ||
| // and `dist/` here, so an untracked-inclusive check would call every | ||
| // correctly-built tree dirty and disable reuse outright — reintroducing | ||
| // the concurrent-shard clobber this fast path exists to prevent. Tracked | ||
| // dirt is what a rewrite leaves, and `npm run build` in this repository | ||
| // modifies no tracked file, so a legitimate build still reuses. | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [Critical] R26-1: [certifies-falsely] [regression] New this round — the tracked-dirt arm's premise is repo-specific, and the failure mode it chooses is destructive. This comment's own concession holds only for this repository: for any OTHER repo this CLI reviews, a build that modifies tracked files (codegen, lockfile rewrites — common) makes the arm fire on a tree THIS run built, and even here a concurrent shard's A/B writing one tracked file (a snapshot Witness: Fix: when The fence comment's own requirement — "Falling through discards the tree (removing the plant with it)" — must still hold for cross-run/planted trees: the busy arm may only engage when the epoch arm matched; and the epoch arm itself is the open R15-4 escalation, so this fix must not be argued as closing the plant path. Fix witness: a base-tree.test.ts case — first run stamps the marker, then ONE tracked file is rewritten, a second run (same cached plan → same epoch) must leave the dirtied file on disk and report busy/unavailable rather than rebuild; removing the busy arm must turn it red (the existing 'does not REUSE … tracked files were rewritten' case flips from rebuild to busy for the same-run shape it exercises). 中文说明本轮新发现——被跟踪脏文件分支的前提是仓库特定的,而它选择的失败模式是破坏性的。这条注释自己的让步只对本仓库成立:对本 CLI 审查的任何其他仓库,修改被跟踪文件的构建(代码生成、锁文件重写——很常见)会让该分支在一次由本次运行构建的树上触发;即使在本仓库,并发分片的 A/B 写入一个被跟踪文件(快照 — qwen3.8-max via Qwen Code /review (v0.23.0) |
||
| gitOut(tree, 'status', '--porcelain', '--untracked-files=no') === '' | ||
|
qwen-code-dev-bot marked this conversation as resolved.
|
||
| ) { | ||
| return { | ||
| available: true, | ||
|
|
@@ -265,6 +311,16 @@ export function runBaseTree(args: BaseTreeArgs): BaseTreeReport { | |
| // Clear a stale base tree left by a crashed run — it would fail `add`. Its | ||
| // stderr is kept, because it is usually what explains that failure. | ||
| sweep = discardWorktree(worktree, tree); | ||
| // The same question the probe phase asks before its own `worktree add`: | ||
| // this resolves the repository through the REVIEW worktree's gitfile, | ||
| // which lives inside the directory the sandbox mounts read-write and | ||
| // which the build/test phase already gave the reviewed code a chance to | ||
| // rewrite. `worktree add` checks files out, so it runs whatever that | ||
| // pointer leads to, on the host. See `untrustedGitfile`. | ||
| const untrusted = untrustedGitfile(worktree); | ||
| if (untrusted !== null) { | ||
| throw new Error(`refusing to create a base tree: ${untrusted}`); | ||
| } | ||
| git(worktree, 'worktree', 'add', '--detach', tree, baseSha); | ||
| } catch (e) { | ||
| return unavailable( | ||
|
|
@@ -335,9 +391,9 @@ export function runBaseTree(args: BaseTreeArgs): BaseTreeReport { | |
| } | ||
|
|
||
| // The marker is what the fast path above trusts, so it is written only after | ||
| // a build that succeeded, and it records the SHA it vouches for. | ||
| // a build that succeeded, and it records the SHA and the run it vouches for. | ||
| try { | ||
| writeFileSync(marker(), `${baseSha}\n`); | ||
| writeFileSync(marker(), `${baseSha}\n${runEpoch}\n`); | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [Critical] R22-1: [fails-closed] The epoch fence this diff adds to the reuse fast path is missing from the sibling failed-marker fast path ten lines above (base-tree.ts:246-249, unchanged): Witness: Fix: mirror the fence — write Fix constraint: base-tree.ts:241-243 — "The marker makes the failure a SETTLED answer for every later shard": a genuine build failure must still answer settled for every later shard of the SAME run (same-run shards share the plan epoch), so the fix must key on the run epoch, not delete the branch. Fix witness: a base-tree.test.ts case in the shape of "does not REUSE a base tree an EARLIER RUN built": first run returns 中文说明本 diff 给复用快路径加的 epoch 栅栏,漏掉了上方十行处的孪生失败标记快路径(base-tree.ts:246-249,未改动): 修复:把栅栏镜像过去——在失败标记写入点写 修复约束:base-tree.ts:241-243——「该标记让失败成为每个后续分片的定论」:真实的构建失败对同一轮次的每个后续分片仍必须定论(同轮分片共享 plan epoch),因此修复必须以轮次 epoch 为键,而不是删除该分支。 修复见证:在 base-tree.test.ts 按「不复用更早一轮构建的 base 树」形状加用例:第一轮返回 — qwen3.8-max via Qwen Code /review (v0.23.0)
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [Critical] R22-1: [certifies-falsely] Still standing (round 22; re-derived independently by both reverse auditors this round and probe-confirmed) — the epoch fence this diff adds to the ok-marker reuse fast path is missing from the sibling failed-marker fast path ten lines below ( Witness: Mirror the ok-marker fence: at the failed-marker write site ( The failed-marker WRITE site must gain the epoch line too, or genuine same-run failures never pass the fenced read; and do not special-case 中文说明仍然成立(第 22 轮;本轮两位反向审计员独立重新推导并经探针确认)——本 diff 给 ok-marker 复用快路径加上的 epoch 栅栏,在下方十行的姊妹 failed-marker 快路径( 修复建议:Mirror the ok-marker fence: at the failed-marker write site ( — qwen3.8-max via Qwen Code /review (v0.23.0)
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [Critical] R22-1: [certifies-falsely] Still standing (rounds 22-24; probe re-confirmed at this head) — the run-epoch fence this diff adds to the ok-marker reuse fast path is missing from the sibling FAILED-marker fast path ten lines above (base-tree.ts:246-249, unchanged): Witness: Fix: fence the failed marker the way the success marker is fenced — write The fence must still accept a legitimate same-run failure marker as a settled answer — base-tree.ts's own rationale: "every shard that asks re-sweeps and re-pays the install+build to relearn the same 'unavailable' — and the sweep destroys the evidence tree the failure deliberately leaves standing." Please add a test planting 中文说明仍然成立(第 22-24 轮;本轮探针再次确认)——本 diff 给 ok-marker 复用快速路径加上的运行纪元围栏,在上方十行的兄弟 FAILED-marker 快速路径(base-tree.ts:246-249,未改动)上是缺失的: 证据:探针——原样代码:植入的失败 marker 被当作既定答案——note「已经构建失败(早先的探针测到过);本次审查无可用 A/B(基础设施问题,绝不作为针对 PR 的发现)」,builds: [];修复(给失败 marker 加 epoch 围栏):重建扫除植入物——note「base tree built at …」。 修复:像给成功 marker 加围栏那样给失败 marker 加围栏——在构建失败臂写入 围栏仍必须把同一运行内合法的失败 marker 当作既定答案接受——base-tree.ts 自己的理由:「每个来询问的分片都会重新清扫、重新支付 install+build 去重新得知同样的『不可用』——而清扫会毁掉失败刻意留下的证据树」。请新增测试:植入 — qwen3.8-max via Qwen Code /review (v0.23.0)
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [Critical] R22-1: [certifies-falsely] Still standing (rounds 22-25; probe re-confirmed rounds 22-24, mechanism re-verified at this head, independently re-derived this round) — the run-epoch fence this diff adds to the ok-marker reuse fast path (anchored here, at the fenced marker write) is missing from the sibling FAILED-marker fast path ( Witness: Fix: fence the failed marker the way the success marker is fenced — write The fence must still accept a legitimate same-run failure marker as a settled answer — base-tree.ts's own rationale: "every shard that asks re-sweeps and re-pays the install+build to relearn the same 'unavailable' — and the sweep destroys the evidence tree the failure deliberately leaves standing." Please add a test planting 中文说明仍然成立(第 22-25 轮;第 22-24 轮探针确认,本轮机制复查通过,且本轮独立重新推导)——本 diff 给 ok-marker 复用快速路径加上的运行纪元围栏(锚定于此,即加了围栏的 marker 写入处),在兄弟 FAILED-marker 快速路径( 证据:第 24 轮探针——原样代码:植入的失败 marker 被当作既定答案——note「已经构建失败(早先的探针测到过);本次审查无可用 A/B(基础设施问题,绝不作为针对 PR 的发现)」,builds: [];修复(给失败 marker 加 epoch 围栏):重建扫除植入物——note「base tree built at …」。第 25 轮:机制在本 head 原样存在;本轮第 2 批验证者作为附带发现独立重新推导。 修复:像给成功 marker 加围栏那样给失败 marker 加围栏——在构建失败臂写入 围栏仍必须把同一运行内合法的失败 marker 当作既定答案接受——base-tree.ts 自己的理由:「每个来询问的分片都会重新清扫、重新支付 install+build 去重新得知同样的『不可用』——而清扫会毁掉失败刻意留下的证据树」。请新增测试:植入 — qwen3.8-max via Qwen Code /review (v0.23.0)
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [Critical] R22-1: [certifies-falsely] Still standing (rounds 22-26; probe re-confirmed at this head) — the run-epoch fence this diff adds to the ok-marker reuse fast path (anchored here, at the fenced marker write) is missing from the sibling FAILED-marker fast path: Witness: Fix: give the failed marker the same epoch fence the ok marker now carries (stamp Fix witness: a case planting a sha-only failed marker and then running with a fresh epoch must NOT settle on "already failed" (the rebuild is attempted); removing the epoch arm must turn it red. 中文说明依然成立(第 22-26 轮;本轮探针再确认)——本 diff 给 ok 标记复用快路径加上的运行 epoch 栅栏(锚点在此,即被栅栏保护的标记写入处),在兄弟 FAILED 标记快路径上缺失: — qwen3.8-max via Qwen Code /review (v0.23.0) |
||
| } catch { | ||
| // The tree may be too broken to hold a marker; the next shard rebuilds. | ||
| } | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[Critical] The lease-sweep rewrite breaks the sync test that pins this exact workflow line.
scripts/tests/review-worktree-cleanup-workflow.test.js(test: 'keeps the review-job cleanup sweep pinned to paths.ts') assertsexpect(reviewCleanStep).toContain(rm -f ${toPosix(REVIEW_TMP_DIR)}/${LEASE_PREFIX}pr-*.json)— the literal substringrm -f .qwen/tmp/qwen-review-lease-pr-*.json. This diff replaced that line with tworm -rflines;rm -f .qwen/tmp/...is not a substring ofrm -rf .qwen/tmp/...(the inserted-rbreaks the match), and these are the only occurrences of the lease glob in the step — so the spec suite is red at HEAD. The lane isnpm run test:scripts, the trailing clause of roottest:ci, run by ci.yml's requiredtestjob on every pull_request and merge_group: this PR's own required check fails on this. Secondarily, nothing pins the new.qwen/review-leasesglob toREVIEW_LEASE_DIR/LEASE_PREFIX, so a later rename of the lease dir would silently no-op the new-location sweep — exactly the drift this spec exists to catch (its header cites a rename that once broke a sweeper).Witness (live run at this commit):
Fix (in the spec file, same PR): import
REVIEW_LEASE_DIRalongsideREVIEW_TMP_DIRand pin both new lines:中文说明
lease 清扫的改写打破了钉住这条工作流行的同步测试。
scripts/tests/review-worktree-cleanup-workflow.test.js(测试「keeps the review-job cleanup sweep pinned to paths.ts」)断言expect(reviewCleanStep).toContain(rm -f ${toPosix(REVIEW_TMP_DIR)}/${LEASE_PREFIX}pr-*.json)——即字面子串rm -f .qwen/tmp/qwen-review-lease-pr-*.json。本 diff 把那一行换成了两条rm -rf行;rm -f .qwen/tmp/...不是rm -rf .qwen/tmp/...的子串(插入的-r破坏了匹配),而这两行是该步骤中 lease 通配符仅有的出现处——因此该规格套件在 HEAD 上是红的。该测试所在通道是npm run test:scripts,即根目录test:ci的收尾环节,由 ci.yml 的必需test作业在每个 pull_request 与 merge_group 上运行:本 PR 自己的必需检查会因此失败。其次,新的.qwen/review-leases通配符没有任何东西钉到REVIEW_LEASE_DIR/LEASE_PREFIX,日后重命名 lease 目录会悄无声息地让新位置的清扫失效——正是这份规格文件存在所要防的漂移(其开头就记录过一次重命名曾弄坏清扫器)。证据(在本提交上实际运行):
npx vitest run --config ./scripts/tests/vitest.config.ts review-worktree-cleanup-workflow→Tests 1 failed | 14 passed——AssertionError: expected 'set -uo pipefail…' to contain 'rm -f .qwen/tmp/qwen-review-lease-pr-…'。修复(在同一 PR 的规格文件中):把
REVIEW_LEASE_DIR与REVIEW_TMP_DIR一并导入,钉住两条新行(见上方英文部分的代码块)。— qwen3.8-max via Qwen Code /review (v0.22.0)