Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 20 additions & 14 deletions integration-tests/cli/acp-integration.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -723,20 +723,26 @@ function setupAcpTest(
// Track which permission requests we've seen
const planModeRequests: PermissionRequest[] = [];

const { sendRequest, cleanup, stderr, sessionUpdates, permissionRequests, agent } =
setupAcpTest(rig, {
permissionHandler: (request) => {
// Track all permission requests for later verification
// Auto-approve exit plan mode requests with "proceed_always" to trigger auto-edit mode
if (request.toolCall?.kind === 'switch_mode') {
planModeRequests.push(request);
// Return proceed_always to switch to auto-edit mode
return { optionId: 'proceed_always' };
}
// Auto-approve all other requests
return { optionId: 'proceed_once' };
},
});
const {
sendRequest,
cleanup,
stderr,
sessionUpdates,
permissionRequests,
agent,
} = setupAcpTest(rig, {
Comment on lines +726 to +733

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Suggestion] This changed test file sits outside every npm workspace, so the workspace-scoped unit-test pass (npm test per package, as run by the unit CI jobs) never collects it — only the dedicated integration job does. — Concrete cost: if the Integration Tests (CLI, No Sandbox) job is skipped on this PR, this changed file ships without ever having executed in the pipeline. It was skipped in this PR's CI. Mitigated here: the hunk is formatting-only (destructuring re-wrap) and an explicit run against the PR bundle passed 11/11 — the residual risk is CI-job coverage of the run, not the code.

Suggested fix: N/A for the code; confirm the integration-tests CI job ran (not skipped) for this PR before merge.

中文说明

[Suggestion] 这个被修改的测试文件位于所有 npm workspace 之外,因此按 workspace 划分的单测通行流程(各 package 的 npm test,即单测 CI 作业所运行的)永远不会收集它——只有专门的集成作业会。— 具体代价:如果 Integration Tests (CLI, No Sandbox) 作业在本 PR 上被跳过,这个被修改的文件就会从未在流水线中执行过就合入。本 PR 的 CI 中该作业确实被跳过了。此处有缓解:该 hunk 仅是格式化(解构重排),且针对 PR bundle 的显式运行 11/11 通过——残余风险在于该运行的 CI 作业覆盖,而非代码本身。

建议修复:代码层面无需改动;合入前确认集成测试 CI 作业确实运行了(而非被跳过)。

— qwen3.8-max via Qwen Code /review (v0.21.9)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Declined — no code change exists: the suggested fix is "N/A for the code" by its own text. The hunk in this file is formatting-only (a destructuring re-wrap), and the review's own explicit run against the PR bundle passed 11/11. The residual risk named — the Integration Tests (CLI, No Sandbox) job being skipped on this PR — is a merge-time check owned by the workflow/maintainer, not something a branch change can fix. Leaving this thread open so the check stays visible at merge time.

中文说明

拒绝——不存在可做的代码改动:建议修复按原文即为 "N/A for the code"。该文件中被改动的 hunk 仅是格式化(解构重排),且评审自己针对 PR bundle 的显式运行 11/11 通过。所指的残余风险——Integration Tests (CLI, No Sandbox) 作业在本 PR 上被跳过——是 workflow/维护者在合入时的检查项,不是分支改动能解决的。线程保持打开,使该检查在合入时保持可见。

permissionHandler: (request) => {
// Track all permission requests for later verification
// Auto-approve exit plan mode requests with "proceed_always" to trigger auto-edit mode
if (request.toolCall?.kind === 'switch_mode') {
planModeRequests.push(request);
// Return proceed_always to switch to auto-edit mode
return { optionId: 'proceed_always' };
}
// Auto-approve all other requests
return { optionId: 'proceed_once' };
},
});

try {
// Initialize
Expand Down
79 changes: 79 additions & 0 deletions packages/cli/src/commands/review/agent-prompt.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ import {
DEADLINE_ENV,
RESERVE_ENV,
COMPOSE_FLOOR_ENV,
TOOL_CONCURRENCY_ENV,
readBudgetStop,
readRoundStamps,
} from './lib/deadline.js';
Expand Down Expand Up @@ -2658,6 +2659,7 @@ describe('the reverse-audit budget gate — the loop must end by reporting', ()
afterEach(() => {
delete process.env[DEADLINE_ENV];
delete process.env[RESERVE_ENV];
delete process.env[TOOL_CONCURRENCY_ENV];
process.exitCode = undefined;
for (const d of dirs.splice(0)) rmSync(d, { recursive: true, force: true });
});
Expand Down Expand Up @@ -3103,6 +3105,59 @@ describe('the reverse-audit budget gate — the loop must end by reporting', ()
// A refusal is not an admission.
expect(readRoundStamps(plan)).toHaveLength(1);
});

it('prices the 3B pair as one admission — round 2 bears the pair wall', () => {
// Round 2's build lands seconds after round 1's stamp, so nothing has
// measured a round yet; the price is both members' wall in waves of the
// tool-concurrency pool. PLAN has three chunks; at a 2-slot pool each
// round runs two waves and the pair three, so round 2 pays 3/2 of the
// round estimate — and the gate refuses it when the reserve plus that
// does not fit, even though round 1 (one estimate) just admitted.
process.env[TOOL_CONCURRENCY_ENV] = '2';
process.env[RESERVE_ENV] = '600';
process.env[DEADLINE_ENV] = String(Math.floor(Date.now() / 1000) + 3000);
const plan = call('reverse-audit', { 'all-chunks': true, round: 1 });
expect(process.exitCode).toBeUndefined();
expect(readRoundStamps(plan).some((st) => st.round === 1)).toBe(true);

(writeStdoutLine as unknown as Mock).mockClear();
call('reverse-audit', { 'all-chunks': true, round: 2 }, plan);
// Reserve 600 + pair price 2700 = 3300 > the 3000 remaining.
expect(process.exitCode).toBe(4);
expect((writeStdoutLine as unknown as Mock).mock.calls).toHaveLength(0);
expect(readBudgetStop(plan)?.entry).toBe(
'reverse audit — stopped before round 2 by the review time budget',
);
expect(readRoundStamps(plan)).toHaveLength(1);
});

it('admits the 3B pair when the reserve plus the pair wall fits', () => {
process.env[TOOL_CONCURRENCY_ENV] = '2';
process.env[RESERVE_ENV] = '600';
process.env[DEADLINE_ENV] = String(Math.floor(Date.now() / 1000) + 3400);
const plan = call('reverse-audit', { 'all-chunks': true, round: 1 });
expect(process.exitCode).toBeUndefined();
(writeStdoutLine as unknown as Mock).mockClear();
call('reverse-audit', { 'all-chunks': true, round: 2 }, plan);
expect(process.exitCode).toBeUndefined();
expect(readRoundStamps(plan).map((st) => st.round)).toEqual([1, 2]);
expect(readBudgetStop(plan)).toBeNull();
});

it('prices the pair at one round when the pool holds both fan-outs at once', () => {
// The default 10-slot pool holds all six auditors of PLAN's 3-chunk
// pair in one wave, so round 2 pays one round estimate — a flat 2x
Comment on lines +3147 to +3149

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Suggestion] This is the only one of the three new pair tests that never pins TOOL_CONCURRENCY_ENV — its two siblings set it explicitly, and repo convention (coreToolScheduler.test.ts: "Ensure tests are deterministic regardless of environment"; Session.test.ts) shields this exact knob because it is documented as operator-tunable. Probe-measured at this commit: with QWEN_CODE_MAX_TOOL_CONCURRENCY exported at 1, 2, or 5, an isolated run of this single test (vitest -t, IDE run-button, single-test rerun) deterministically fails with expected 4 to be undefined — the pair price doubles past the 3000 deadline although the code under test is correct. Full-suite runs self-heal (a preceding test's afterEach deletes the ambient key), which is how this escaped. — Failure scenario: a developer or runner exporting the documented pool knob at ≤ 5 gets a spurious failure indicting the budget gate instead of the environment.

Suggested fix (verified by probe to flip the failing arm green):

it('prices the pair at one round when the pool holds both fan-outs at once', () => {
  // …
  delete process.env[TOOL_CONCURRENCY_ENV]; // shield the ambient before the builds
  process.env[RESERVE_ENV] = '600';
中文说明

这是三个新配对测试中唯一没有钉住 TOOL_CONCURRENCY_ENV 的——它的两个兄弟测试都显式设置该值,且仓库惯例(coreToolScheduler.test.ts:“Ensure tests are deterministic regardless of environment”;Session.test.ts)会屏蔽这个确切的旋钮,因为它被文档标注为运维可调。在本提交上实测:当导出 QWEN_CODE_MAX_TOOL_CONCURRENCY 为 1、2 或 5 时,单独运行该测试(vitest -t、IDE 运行按钮、单测重跑)会确定性地以 expected 4 to be undefined 失败——配对价格翻倍越过 3000 deadline,尽管被测代码是正确的。全套件运行能自愈(前面测试的 afterEach 删除了环境键),这正是它未被发现的原因。—— 失败场景:导出该文档化池旋钮 ≤ 5 的开发者或运行器得到一个错误归因于预算门而非环境的假失败。

建议修复(探针已验证可使失败分支转绿,见上方英文代码块)。

— qwen3.8-max via Qwen Code /review (v0.21.10)

// price would refuse this admission (reserve 600 + 3600 > 3000) and
// gut the pair's admission win near the deadline.
process.env[RESERVE_ENV] = '600';
process.env[DEADLINE_ENV] = String(Math.floor(Date.now() / 1000) + 3000);
const plan = call('reverse-audit', { 'all-chunks': true, round: 1 });
expect(process.exitCode).toBeUndefined();
(writeStdoutLine as unknown as Mock).mockClear();
call('reverse-audit', { 'all-chunks': true, round: 2 }, plan);
expect(process.exitCode).toBeUndefined();
expect(readRoundStamps(plan).map((st) => st.round)).toEqual([1, 2]);
});
});

describe('per-chunk retirement — cold territories stop costing a round', () => {
Expand Down Expand Up @@ -3310,6 +3365,30 @@ describe('per-chunk retirement — cold territories stop costing a round', () =>
expect(keysOf(2)).toHaveLength(3);
});

it('the 3B pair: round 2 builds every chunk with round 1 still in flight (no round-1 transcripts)', () => {

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Suggestion] This test proves that both prompt builders can run without round-1 transcripts, but it does not pin the effect-bearing SKILL.md instruction that both 3B rounds must launch in the same response. — Concrete cost: a later edit can serialize the skill while this test stays green because it calls runRound(1) and runRound(2) itself, restoring the extra reverse-audit round wall. Add a bounded assertion in packages/core/src/skills/bundled/review/SKILL.test.ts that the 3B section contains both --all-chunks --round commands and in the same response.

中文说明

这个测试证明了两个 prompt builder 可以在没有 round-1 transcript 时运行,但没有固定真正影响行为的 SKILL.md 约束:两个 3B rounds 必须在同一个 response 中启动。具体代价:后续可以把 skill 改回串行,而该测试仍然通过,因为它自己顺序调用 runRound(1)runRound(2),从而重新增加一个 reverse-audit round wall。建议在 packages/core/src/skills/bundled/review/SKILL.test.ts 中加入有界断言,要求 3B section 同时包含两个 --all-chunks --round 命令和 in the same response

— Qwen Code via Qwen Code /review (v0.21.9)

// The convergence pair on 3B — the latency lever: rounds 1 and 2 are
// launched together, so round 2's builder runs BEFORE round 1's auditors
// have returned any transcript. Round 2 must still fan out to every chunk
// (the retirement schedule only reads history at k >= 3, so nothing here
// depends on round 1's records existing) and stamp its own admission, so
// the two rounds' auditors run concurrently instead of one round-wall
// apart. Pins the mechanism the SKILL 3B-pair orchestration relies on.
const r1 = runRound(1); // built, but no transcripts written for it
expect(r1).toContain('3 auditors required this round — one per chunk.');
const r2 = runRound(2); // round 1's transcripts don't exist yet at this point
expect(r2).toContain('3 auditors required this round — one per chunk.');
expect(r2).not.toContain('retirement:');
expect(keysOf(1)).toHaveLength(3);
expect(keysOf(2)).toHaveLength(3);
// Both admissions are stamped, so the deadline gate prices each and the
// clock advances a round per stamp.
const rounds = readRoundStamps(plan)
.map((s) => s.round)
.sort();
expect(rounds).toContain(1);
expect(rounds).toContain(2);
});

it('round 3 skips a chunk dry in rounds 1 and 2, and the note names it', () => {
answerRound(1, { 13: DRY, 14: YIELD, 15: YIELD });
answerRound(2, { 13: DRY, 14: YIELD, 15: YIELD });
Expand Down
27 changes: 20 additions & 7 deletions packages/cli/src/commands/review/agent-prompt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ import { writeStdoutLine, writeStderrLine } from '../../utils/stdioHelpers.js';
import { launchToolBudget, reverseAuditRoundCap } from './lib/budget.js';
import {
clearBudgetStop,
expectedRoundSeconds,
expectedAdmissionSeconds,
readRoundStamps,
reverseAuditBudgetExhausted,
reverseAuditBudgetMessage,
Expand Down Expand Up @@ -1862,12 +1862,17 @@ function requireAuditableChunks(report: PlanReport): DiffChunk[] {
* round was refused: the caller builds nothing. The admission STAMP is not
* written here — it lands after the build succeeds, in each build path: the
* stamp is what the next round's gate measures cost from, and a build that
* throws must not leave one behind.
* throws must not leave one behind. `fanOutWidth` is the auditors this
* round fans out (1 for a whole-diff round): when the previous round is
* still in flight — the convergence pair's second member — the price
* covers both members' wall in waves of the tool-concurrency pool, not
* just this round's (deadline.ts `expectedAdmissionSeconds`).
*/
function admitReverseAuditRound(
planPath: string,
round: number | undefined,
cap: number,
fanOutWidth: number,
): boolean {
// The plan's round cap first: deterministic, and cheaper than the
// deadline arithmetic. The full cap normally; a reduced cap for a huge
Expand Down Expand Up @@ -1900,7 +1905,7 @@ function admitReverseAuditRound(
}
const spent = reverseAuditBudgetExhausted(
process.env,
expectedRoundSeconds(planPath, round),
expectedAdmissionSeconds(planPath, round, fanOutWidth, process.env),
);
if (spent !== null) {
writeBudgetStop(planPath, spent, round);
Expand Down Expand Up @@ -2009,6 +2014,7 @@ function runAllChunks(
planPath,
round,
reverseAuditRoundCap(report.budget),
chunks.length,
)
) {
return;
Expand Down Expand Up @@ -2403,7 +2409,9 @@ function runAgentPrompt(args: AgentPromptArgs): void {
// admits on the reserve alone hands the terminal round a start right at
// the boundary, which is the killed-mid-verification failure one round
// wide. The round's cost is the previous round's, measured admission to
// admission. The admission is stamped AFTER the build succeeds (below),
// admission — except when this round launches with the previous one still
// in flight (the convergence pair), where it covers both. The admission is
// stamped AFTER the build succeeds (below),
// never here: the stamp is what the next round's gate measures cost from,
// and a build that throws must not leave one behind — priced from a
// failed build, the next round would be floored to the 600s minimum,
Expand All @@ -2421,6 +2429,7 @@ function runAgentPrompt(args: AgentPromptArgs): void {
args.plan,
args.round,
reverseAuditRoundCap(report.budget),
1,
)
) {
return;
Expand Down Expand Up @@ -2471,14 +2480,17 @@ function runAgentPrompt(args: AgentPromptArgs): void {
hasChunk &&
!readRoundStamps(args.plan).some((s) => s.round === (args.round ?? null))
) {
const planChunkIds = (
Array.isArray(report.chunks) ? (report.chunks as DiffChunk[]) : []
)
.map((c) => c?.id)
.filter((id): id is number => typeof id === 'number');
if (args.round !== undefined) {
let schedule: RoundSchedule | null = null;
try {
schedule = scheduleReverseAuditRound(
args.plan,
(Array.isArray(report.chunks) ? (report.chunks as DiffChunk[]) : [])
.map((c) => c?.id)
.filter((id): id is number => typeof id === 'number'),
planChunkIds,
args.round,
process.env,
typeof report.diffPathAbsolute === 'string'
Expand All @@ -2500,6 +2512,7 @@ function runAgentPrompt(args: AgentPromptArgs): void {
args.plan,
args.round,
reverseAuditRoundCap(report.budget),
planChunkIds.length,
)
Comment on lines 2512 to 2516

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Suggestion] The fanOutWidth wiring is untested at two of the three admission call sites — the whole-diff 3A site (literal 1, ~L2433) and this --chunk first-build site (planChunkIds.length): every width-sensitive test in the diff drives runAllChunks (--all-chunks), and the existing --chunk gate tests use expired or far-future deadlines where any price gives the same outcome. — Failure scenario: a future refactor passing a narrower width here survives the whole suite — on the default 10-slot pool widths 1–3 price identically for the 3-chunk fixture plan (equivalent mutant). The mutant becomes observable only under QWEN_CODE_MAX_TOOL_CONCURRENCY=2 with a fresh round-1 stamp, where the first --chunk 13 --round 2 build of an unadmitted round would price 1800s instead of 2700s, admitting a pair that does not fit — the killed-before-compose failure this gate exists to prevent. (The untested wiring is correct as written — this is a missing guard, not a live defect.)

Suggested fix: add one handler-level test admitting round 1 via --all-chunks and building round 2's first auditor via { chunk: 13, round: 2 } at pool 2, asserting exit 4 at 3000s remaining and admission at 3400s; optionally a 3A twin ({ round: 1 } then { round: 2 }, width-1 price).

中文说明

[Suggestion] fanOutWidth 的接线在三个准入调用点中的两个没有测试覆盖——整 diff 的 3A 调用点(字面量 1,约 L2433)和这个 --chunk 首次构建调用点(planChunkIds.length):diff 中所有对宽度敏感的测试都驱动 runAllChunks--all-chunks),而既有的 --chunk 门测试使用的是已过期或远未来的截止时间,任何价格都得到相同结果。— 失败场景:未来一次在这里传入更窄宽度的重构能活着通过整个测试套件——在默认 10 槽池上,对 3 块 fixture 计划宽度 1–3 的定价完全相同(等价突变体)。该突变只有在 QWEN_CODE_MAX_TOOL_CONCURRENCY=2 且存在新鲜 round-1 戳记时才可观测:未准入轮次的首个 --chunk 13 --round 2 构建会定价 1800 秒而非 2700 秒,从而放行一个容纳不下的配对——正是这个门要防止的 compose 前被杀失败。(未测试的接线本身是正确的——这是缺失的防护,不是现存缺陷。)

建议修复:新增一个 handler 级测试——通过 --all-chunks 准入 round 1,再以 { chunk: 13, round: 2 } 构建 round 2 的首个审计员,池为 2,断言剩余 3000 秒时 exit 4、3400 秒时放行;可选再加一个 3A 孪生测试({ round: 1 }{ round: 2 },宽度 1 的价格)。

— qwen3.8-max via Qwen Code /review (v0.21.9)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Deferred this round (not dropped): the wiring is correct as written — the finding itself classifies this as a missing guard, not a live defect — and this round landed the cheaper fixes under a budget warning. Test recipe preserved for the follow-up: one handler-level test admitting round 1 via --all-chunks, then building round 2's first auditor via { chunk: 13, round: 2 } under QWEN_CODE_MAX_TOOL_CONCURRENCY=2 with a fresh round-1 stamp, asserting exit 4 at 3000s remaining and admission at 3400s; optionally the 3A twin ({ round: 1 } then { round: 2 }, width-1 price).

中文说明

本轮推迟(并非丢弃):接线按现状是正确的——发现本身将其归类为缺失的防护,而非现存缺陷——且本轮在预算警告下落地了成本较低的修复。为后续保留的测试配方:一个 handler 级测试,先通过 --all-chunks 准入 round 1,再在 QWEN_CODE_MAX_TOOL_CONCURRENCY=2 且存在新鲜 round-1 戳记的条件下以 { chunk: 13, round: 2 } 构建 round 2 的首个审计员,断言剩余 3000 秒时 exit 4、3400 秒时放行;可选再加 3A 孪生测试({ round: 1 }{ round: 2 },宽度 1 的价格)。

)
return;
Expand Down
114 changes: 114 additions & 0 deletions packages/cli/src/commands/review/lib/deadline.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,12 @@ import {
DEFAULT_RESERVE_SECONDS,
DEFAULT_ROUND_SECONDS,
DEFAULT_COMPOSE_FLOOR_SECONDS,
DEFAULT_TOOL_CONCURRENCY,
TOOL_CONCURRENCY_ENV,
budgetStopEntry,
budgetStopEntryZh,
clearBudgetStop,
expectedAdmissionSeconds,
expectedRoundSeconds,
readBudgetStop,
readRoundStamps,
Expand Down Expand Up @@ -282,6 +285,117 @@ describe('the round-cost estimate — measured when it can be', () => {
});
});

describe('the pair admission price — a round launched beside an in-flight round pays for both', () => {
// The convergence pair's second member is built seconds after the first's
// stamp, so nothing has measured a round yet. Pricing it off that
// seconds-old span committed the pair at one round's price for up to two
// rounds' wall — these pin the wave-priced pair instead.
const dirs: string[] = [];
afterEach(() => {
for (const d of dirs.splice(0)) rmSync(d, { recursive: true, force: true });
});
function plan(): string {
const dir = mkdtempSync(join(tmpdir(), 'deadline-pair-'));
dirs.push(dir);
const p = join(dir, 'plan.json');
writeFileSync(p, '{}');
backdatePlan(p);
return p;
}

it('prices a round with no in-flight predecessor like expectedRoundSeconds', () => {
const p = plan();
expect(expectedAdmissionSeconds(p, 1, 6, {}, NOW_MS)).toBe(
DEFAULT_ROUND_SECONDS,
);
stampRound(p, 1, NOW_MS - 2_400_000); // round 1 returned 40 min ago
expect(expectedAdmissionSeconds(p, 2, 6, {}, NOW_MS)).toBe(
expectedRoundSeconds(p, 2, NOW_MS),
);
expect(expectedAdmissionSeconds(p, 2, 6, {}, NOW_MS)).toBe(2400);
});

it('prices the pair at both members when the pool serializes them', () => {
// Six chunks on the default 10-slot pool: one wave per round, two
// waves for the pair — the seconds-old round-1 stamp has measured
// nothing, so the price is 2x the round estimate, not the floor.
const p = plan();
stampRound(p, 1, NOW_MS - 30_000);
expect(expectedAdmissionSeconds(p, 2, 6, {}, NOW_MS)).toBe(
2 * DEFAULT_ROUND_SECONDS,
);
});

it('prices the pair at one round when the pool holds both members at once', () => {
// Three chunks on ten slots: both members fit in a single wave, and
// the pair's wall is one round's — the 3A shape reads the same (width
// 1 on any pool of two or more).
const p = plan();
stampRound(p, 1, NOW_MS - 30_000);
expect(expectedAdmissionSeconds(p, 2, 3, {}, NOW_MS)).toBe(
DEFAULT_ROUND_SECONDS,
);
expect(expectedAdmissionSeconds(p, 2, 1, {}, NOW_MS)).toBe(
DEFAULT_ROUND_SECONDS,
);
});

it('reads the pool from the tool-concurrency env, like the scheduler', () => {
const p = plan();
stampRound(p, 1, NOW_MS - 30_000);
// A 12-slot pool holds all twelve auditors of a 6-chunk pair in one
// wave.
expect(
expectedAdmissionSeconds(
p,
2,
6,
{ [TOOL_CONCURRENCY_ENV]: '12' },
NOW_MS,
),
).toBe(DEFAULT_ROUND_SECONDS);
// A 3-slot pool runs a 6-chunk round in two waves and the pair in
// four — two rounds' price again.
expect(
expectedAdmissionSeconds(
p,
2,
6,
{ [TOOL_CONCURRENCY_ENV]: '3' },
NOW_MS,
),
).toBe(2 * DEFAULT_ROUND_SECONDS);
// Malformed falls back to the default pool, never to a wedge.
expect(
expectedAdmissionSeconds(
p,
2,
6,
{ [TOOL_CONCURRENCY_ENV]: 'soon' },
NOW_MS,
),
).toBe(
Math.ceil(
(DEFAULT_ROUND_SECONDS * Math.ceil(12 / DEFAULT_TOOL_CONCURRENCY)) /
Math.ceil(6 / DEFAULT_TOOL_CONCURRENCY),
Comment on lines +378 to +380

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Suggestion] Every default-pool oracle in this new suite recomputes its expected value from DEFAULT_TOOL_CONCURRENCY itself, so the one constant coupling the gate's pool model to the runtime's is unpinned. Mutation run at this commit: DEFAULT_TOOL_CONCURRENCY = 10 → 11 survives the whole diff's test suite (deadline.test.ts 49/49 and the pair tests 7/7 stay green) while the runtime pool stays the literal 10 in coreToolScheduler.ts. — Failure scenario: a future edit of the constant (or drift against the scheduler's literal) silently misprices pairs in production — a 16-chunk fan-out prices at ceil(32/11)/ceil(16/11) = 1.5× instead of the real ceil(32/10)/ceil(16/10) = 2× waves — admitting pairs near the deadline on less wall than they consume, with all tests green.

Suggested fix: pin one oracle to a literal at a discriminating width (width 16 diverges between pools 10 and 11), or assert DEFAULT_TOOL_CONCURRENCY equals the scheduler's literal 10:

expect(
  expectedAdmissionSeconds(p, 2, 16, {}, NOW_MS),
).toBe(3600); // literal, not DEFAULT_TOOL_CONCURRENCY-derived: pool 10 ⇒ 2 waves
中文说明

这个新套件中所有默认池的期望值都用 DEFAULT_TOOL_CONCURRENCY 本身重新计算,因此把门的池模型与运行时耦合起来的这个唯一常量没有被钉住。在本提交上执行变异测试:DEFAULT_TOOL_CONCURRENCY = 10 → 11 在整个 diff 的测试套件下存活(deadline.test.ts 49/49、配对测试 7/7 仍然全绿),而运行时池仍是 coreToolScheduler.ts 中的字面量 10。—— 失败场景:未来对该常量的修改(或与调度器字面量的漂移)会在生产中悄无声息地错误定价配对——16 个 chunk 的 fan-out 按 ceil(32/11)/ceil(16/11) = 1.5× 定价,而真实波数是 ceil(32/10)/ceil(16/10) = 2×——在临近 deadline 时以少于实际消耗的墙钟放行配对,且所有测试保持绿色。

建议修复:用一个可区分的宽度把某个期望值钉成字面量(宽度 16 在池 10 与池 11 之间结果不同),或断言 DEFAULT_TOOL_CONCURRENCY 等于调度器的字面量 10(见上方英文代码块)。

— qwen3.8-max via Qwen Code /review (v0.21.10)

),
);
});

it('keeps the reserve on top of the pair price at the refusal boundary', () => {
const p = plan();
stampRound(p, 1, NOW_MS - 30_000);
const price = expectedAdmissionSeconds(p, 2, 6, {}, NOW_MS);
expect(price).toBe(2 * DEFAULT_ROUND_SECONDS);
const env = {
[DEADLINE_ENV]: String(NOW_S + DEFAULT_RESERVE_SECONDS + price),
};
expect(reverseAuditBudgetExhausted(env, price, NOW_MS)).toBeNull();
env[DEADLINE_ENV] = String(NOW_S + DEFAULT_RESERVE_SECONDS + price - 1);
expect(reverseAuditBudgetExhausted(env, price, NOW_MS)).not.toBeNull();
});
});

describe('the budget-stop marker — the deterministic half of the disclosure', () => {
const dirs: string[] = [];
afterEach(() => {
Expand Down
Loading
Loading