Skip to content

fix(serve): Harden standalone conversation primitives - #9512

Merged
doudouOUC merged 4 commits into
QwenLM:mainfrom
doudouOUC:fix/standalone-pr2a-followups
Aug 20, 2026
Merged

fix(serve): Harden standalone conversation primitives#9512
doudouOUC merged 4 commits into
QwenLM:mainfrom
doudouOUC:fix/standalone-pr2a-followups

Conversation

@doudouOUC

@doudouOUC doudouOUC commented Aug 19, 2026

Copy link
Copy Markdown
Collaborator

What this PR does

Fixes items 3, 4, and 5 tracked in #9490 from the review of the standalone conversation isolation primitives:

  1. Directory race adoption (item 3). When a standalone directory is created by a concurrent actor inside the ensure race window, it is now adopted only after passing the same inspection used for a directory found up front. A first-ever creation reports created, while recreated is reserved for a directory that vanished after its identity was captured.
  2. Integrity and record budgets (item 4). The integrity-aware JSONL head reader measures its budget in physical non-empty lines, so its fail-closed verdict always covers a fixed prefix even when one malformed line contains multiple recoverable records. The plain head reader keeps its separate record-budget contract: malformed, scalar, and blank lines do not consume the budget, glued records do, and the result never exceeds the requested record count.
  3. Error cause serialization (item 5). Directory-identity errors pass their cause through the native ES2022 error options bag. Instances without a cause no longer expose an enumerable cause: undefined, while an actual cause remains readable and non-enumerable.

Why it's needed

These defects were reported during the review of #9341 and deferred to keep that PR bounded; #9490 tracks the follow-up work. The directory ensure and inspection primitives do not yet have production consumers, but PR2B will build directly on their race and status contracts. The integrity reader already gates session-head classification, so its coverage must be deterministic without changing the long-standing behavior of the plain record-limited reader.

Reviewer Test Plan

How to verify

  • Raced adoption: when the first inspection reports missing and a concurrent creator wins before materialization, a second inspection must run even without an expected identity, and any compromised verdict must be propagated.
  • Status semantics: a first creation reports created; recreation after a captured identity disappears reports recreated.
  • Integrity budget: corruption within the first N non-empty lines reports incomplete even when an earlier glued line already recovered N records, and every record recovered from those scanned lines is returned.
  • Plain reader compatibility: malformed and scalar lines that recover no records do not consume its record budget, and glued lines never make it return more than N records.
  • Cause behavior: without a cause there is no own cause key; with a cause the value is readable but absent from Object.keys and JSON.stringify.

The targeted suites pass locally: cd packages/cli && npx vitest run src/utils/conversation-directory-identity.test.ts src/serve/conversations/conversation-workspace.test.ts (29 tests) and cd packages/core && npx vitest run src/utils/jsonl-utils.test.ts src/services/sessionService.test.ts src/services/sessionService.corruption.test.ts (226 tests). npm run typecheck --workspace=packages/core, Prettier, ESLint, and git diff --check also pass for the changed scope.

Evidence (Before & After)

N/A — non-UI behavioral fixes.

Tested on

OS Status
🍏 macOS
🪟 Windows ⚠️
🐧 Linux ⚠️

Environment (optional)

macOS 26.4.1, Node.js v22.22.3, npm 10.9.8; unit tests only.

Risk & Scope

  • Main risk or tradeoff: the ensure result union gains a created member, but that primitive has no production consumers yet. The integrity-aware reader can return more records than its numeric line budget when one physical line contains glued records; its single production consumer treats the result as a scan window, while the plain reader retains an explicit record cap.
  • Not validated / out of scope: PR2A follow-ups: enforcement breadth, lock-key folding, directory race adoption, integrity budget, error serialization #9490 item 1 belongs with PR2B's trusted reserved-source write seam; item 2 requires lock identity derived from persisted storage spelling, rather than global lowercasing, and is expected to align with Session lifecycle operations are gated on provenance classification, making unclassifiable sessions unmanageable #9488. This PR also keeps the existing aggregate fail-closed metadata policy: corruption later in the inspected prefix can still make earlier creation metadata unreadable. Full-workspace build and typecheck were not used as local evidence because this shared checkout has unrelated Ink and WebUI dependency-resolution mismatches; GitHub CI remains the workspace-wide signal.
  • Breaking changes / migration notes: none.

Linked Issues

中文说明

本 PR 做了什么

修复 #9490 中追踪的 item 3、4、5,这些问题来自对 standalone conversation 隔离原语的评审:

  1. 目录竞态收养(item 3)。 当 standalone 目录在 ensure 竞态窗口内被并发创建者抢先创建时,现在只有通过与“初始即存在的目录”相同的检查后才会被采纳。首次创建返回 createdrecreated 只用于身份已捕获后目录消失并被重建的场景。
  2. 完整性与记录预算(item 4)。 完整性感知的 JSONL 头部读取器按物理非空行计量预算,因此即使一条损坏行包含多条可恢复记录,其 fail-closed 裁决也始终覆盖固定文件前缀。普通头部读取器继续保持独立的记录预算契约:malformed、scalar 和空行不消耗预算,粘连记录会消耗预算,返回结果永远不会超过请求的记录数。
  3. 错误 cause 序列化(item 5)。 目录身份错误通过 ES2022 原生 error options bag 传递 cause。没有 cause 的实例不再暴露可枚举的 cause: undefined,实际 cause 则保持可读且不可枚举。

为什么需要

这些缺陷在 #9341 评审期间被报告,为控制该 PR 的范围而推迟,由 #9490 跟踪后续工作。目录 ensure 和 inspection 原语尚无生产消费者,但 PR2B 将直接依赖其竞态和状态契约。完整性读取器已经用于 session head 分类,因此其覆盖范围必须保持确定,同时不能改变普通记录限额读取器的长期行为。

审阅者测试计划

如何验证

  • 竞态收养:首次检查报告 missing、并发创建者在 materialization 前抢先创建时,即使没有 expected identity 也必须执行第二次检查,并传播任何 compromised 裁决。
  • 状态语义:首次创建报告 created;已捕获 identity 消失后的重建报告 recreated
  • 完整性预算:只要损坏位于前 N 个非空物理行内,即使更早的粘连行已经恢复出 N 条记录,也必须报告 incomplete,并返回扫描窗口内恢复出的全部记录。
  • 普通读取器兼容性:没有恢复出记录的 malformed 和 scalar 行不消耗记录预算,粘连行也不能让结果超过 N 条记录。
  • Cause 行为:不传 cause 时没有自有 cause 键;传入 cause 时值可读,但不会出现在 Object.keysJSON.stringify 中。

定向测试在本地通过:cd packages/cli && npx vitest run src/utils/conversation-directory-identity.test.ts src/serve/conversations/conversation-workspace.test.ts(29 个测试)和 cd packages/core && npx vitest run src/utils/jsonl-utils.test.ts src/services/sessionService.test.ts src/services/sessionService.corruption.test.ts(226 个测试)。变更范围内的 npm run typecheck --workspace=packages/core、Prettier、ESLint 和 git diff --check 也均通过。

证据(Before & After)

N/A —— 非 UI 行为修复。

测试平台

OS 状态
🍏 macOS
🪟 Windows ⚠️
🐧 Linux ⚠️

环境(可选)

macOS 26.4.1、Node.js v22.22.3、npm 10.9.8;仅运行单元测试。

风险与范围

关联 Issue

@qwen-code-ci-bot

qwen-code-ci-bot commented Aug 20, 2026

Copy link
Copy Markdown
Collaborator

🩺 serve daemon A/B

Built the PR base vs this PR head db27aea, drove a fixed endpoint set against each, and diffed the JSON responses. Only fields that changed are shown.

No response changes against the PR base across 4 scenario(s).

Qwen Code · serve A/B

@doudouOUC
doudouOUC force-pushed the fix/standalone-pr2a-followups branch from ec845c4 to 240a196 Compare August 20, 2026 06:49
@github-actions

Copy link
Copy Markdown
Contributor

Please do not rebase or force-push to an active PR as it invalidates existing review comments. Note for future reference, the bots always squash all changes into a single commit automatically as part of the integration.

中文

请勿对活跃的 PR 执行 rebase 或 force-push,因为这会使已有的评审评论失效。另外,供日后参考:作为集成流程的一部分,机器人始终会自动将所有改动压缩(squash)为单个提交。

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
@doudouOUC doudouOUC changed the title fix: Harden standalone PR2A primitives (race adoption, integrity budget, cause serialization) fix(serve): Harden standalone conversation primitives Aug 20, 2026
@doudouOUC
doudouOUC marked this pull request as ready for review August 20, 2026 07:41
@doudouOUC
doudouOUC enabled auto-merge August 20, 2026 07:41
@doudouOUC

Copy link
Copy Markdown
Collaborator Author

@qwen-code /triage

@qwen-code-ci-bot

qwen-code-ci-bot commented Aug 20, 2026

Copy link
Copy Markdown
Collaborator

Qwen Triage finished — CI landed green on db27aea and the deferred approval was posted. finalize run

Qwen Triage 已完成 —— db27aea 的 CI 全绿,延迟审批已提交。查看 finalize 运行

@qwen-code-ci-bot

qwen-code-ci-bot commented Aug 20, 2026

Copy link
Copy Markdown
Collaborator

Sandboxed verification: ✅ passed — merge-ready (agent verdict) - workflow run

Ran the PR in an isolated, token-free container: A/B against the base build, mock-free harness assertions, targeted gates. Advisory evidence for human reviewers — not a review, an approval, or a CI check.

Scripted assertions: 71 passed · 0 failed · 71 total

Flakiness gate: ✅ 3 changed test file(s) x 5 identical rounds, no divergence

中文 — 判定:✅ 通过 · 可合入(agent 判定)

沙箱验证在隔离、无凭证的容器中执行了该 PR 的代码(与 base 构建 A/B 对照、无 mock harness 断言、定向门禁)。仅作为评审证据,不构成评审、批准或 CI 检查

脚本断言:71 通过 · 0 失败 · 71 总计

抖动门:✅ 3 changed test file(s) x 5 identical rounds, no divergence

Verification report

PR #9512 — fix(serve): Harden standalone conversation primitives

Verdict: merge-ready — 71/71 scripted assertions passed (0 unexpected failures) at verified head db27aea2b6a49084f514b6c5cfee0b4e3a1f9e91 (merge-ref checkout HEAD^2; base arm HEAD^1 = 02d303f8).

中文摘要
  • 结论: merge-ready。71/71 脚本化断言通过,0 个意外失败。
  • A/B 结论: 三个修复均被证明 load-bearing。核心修复(JSONL 完整性读取器按物理非空行计量预算)在 base 上可复现缺陷:当第 1 行是 }{ 粘连记录、已满足记录预算时,第 2/9 行的损坏被 base 漏报(complete: true),head 正确 fail-closed(complete: false)——该差异在读取器层(15 个 cell)和真实生产消费点 SessionService.readCreationMetadataIfReadable(2 个 cell)都被证实。竞态收养修复在 base 上表现为:无 expected 时盲采竞态目录(1 次检查、双消失场景返回 ready),head 重新检查并 fail-closed;首次创建状态 base 误报 recreated,head 报 created。cause 修复在 base 上表现为无 cause 实例带可枚举 cause: undefined(泄漏进 Object.keys/spread),head 无此键。
  • Findings: 无阻塞项。两条信息级观察:① readLines.slice(0, count) 是死子句(M2 突变存活 40/40,记录预算守卫已保证不超量,base 无 slice 输出逐字节相同)——完整性报告,非合并条件;② 传入伪造 expected 且目录从未存在时 ensure 仍报 recreated——既有语义宽松,本 PR 未改变,且调用方为受信内部代码。
  • 未覆盖: 逐 commit 归因(shallow checkout,元数据 4 个 commit 本地仅可达 1 个,验证的是聚合 diff);Windows/macOS 文件系统语义;全仓 build/typecheck(仅两个受影响 workspace);PR2A follow-ups: enforcement breadth, lock-key folding, directory race adoption, integrity budget, error serialization #9490 item 1-2(作者明确出范围)。

Scope

Central claim (item 4): the integrity-aware JSONL head reader measures its budget in physical non-empty lines, so its fail-closed verdict covers a fixed line prefix even when a glued }{ line satisfies the record budget early. This is the highest-value item because readLinesWithIntegrity has a live production consumer (SessionService.readCreationMetadataInternal, fail-closed creation-metadata classification, MAX_PROMPT_SCAN_LINES = 10).

Secondary claims: (item 3) raced standalone directories are adopted only through a re-inspection even without an expected identity, and first-ever creation reports created (not recreated); (item 5) identity errors carry cause via the ES2022 options bag — no enumerable cause: undefined without one, readable non-enumerable with one.

A/B results

Environment per cell: head = compiled packages/*/dist at HEAD^2; base = git worktree at HEAD^1 compiled with the same TypeScript (core: full package emit; CLI: the exact 3-file closure). Control purity asserted: the tested closures contain no @qwen-code/* imports (no internal-workspace symlink trap), package.json/package-lock.json are untouched by the PR, and shared external deps resolve to one install (realpaths quoted in methodology). Raw logs in logs/.

Central claim — JSONL budgets (harness 01-jsonl-ab.mjs, witness 01-jsonl-ab-base-vs-head.png)

cell fixture (count) base oracle head oracle result
C1 central {"i":1}{"i":2}\n{"i":\n (2) complete: true — corruption missed complete: false base defect / head fixed
C2 glued pair + clean + {"i":4}garbage (3) complete: true — corruption missed complete: false, returns all 4 recovered base defect / head fixed
C3 glued pair + clean (1) 1 record (record budget) both records of the scanned line contract split as documented
C8 3-glued + clean (2) 2 records 4 records (whole scan window) documented tradeoff holds
C4–C7, C9 blanks / clean / short / count=0 / scalar parity parity 5/5 parity
P1–P6 plain reader: glued, zero-record lines, overflow, blanks, scalars, count=1 parity parity (slice never truncates) 6/6 parity

15/15 cells pass per arm. The PR's own new tests pin exactly this: mutant M1 (revert to record budget) is killed by the 3 budget tests with the behavioral mismatch complete: true vs false (05-mutation-matrix.png).

Consumer-level proof (harness 04-consumer-ab.mjs, witness 04-consumer-ab-base-vs-head.png)

Real SessionService.readCreationMetadataIfReadable driven on a session file whose first 5 physical lines carry 10 glued records and whose line 9 is truncated:

cell base head
S1 corruption at line 9 behind a full record budget returns {parentSessionId: 'parent-0001', sourceType: 'default', …} — reads creation metadata from a corrupt transcript undefined — fails closed
S2 clean head, 12 records in the 10-line scan window identical metadata identical metadata (consumer tolerates the wider scan window)

Raced adoption + status semantics (harness 02-ensure-ab.mjs, witness 02-ensure-ab-base-vs-head.png)

cell base head
E1 first-ever creation, no expected recreated (mislabel) created
E2 raced clean dir, no expected blind ready, 1 inspection ready after re-inspection (2 calls)
E6 double-vanish inside the race window blind ready compromised/identity_changed (fail-closed)
E3/E4/E5 parity (wrong-mode rejected at materialize; stale expected → unexpected_identity; vanished known identity → recreated) parity

6/6 per arm. Mutants M4 (restore && expected) and M5 (always recreated) are each killed by the corresponding new tests; M4's failure shows the compromised verdict silently replaced by created on base's guard.

Cause serialization (harness 03-cause-ab.mjs, witness 03-cause-ab-base-vs-head.png)

cell base head
K1 no cause 'cause' in e = true, enumerable, in Object.keys no own cause key at all
K3 explicit undefined cause class-field leak no key
K7 {...e} spread leaks cause key clean
K2/K4/K5/K6 parity (readable non-enumerable cause; scalar cause; identity; liveIdentityError unwrap seam) parity

7/7 per arm. Mutant M6 (re-add the class field) is killed by the new cause test and by the pre-existing preserves Live filesystem errors… unwrap test — the cause semantics has a live consumer.

Mutation matrix (scripted adjudication 05-matrix-check.mjs, witness 05-mutation-matrix.png)

mutant guard suite outcome classification
M1 revert integrity budget to records line budget core jsonl+corruption killed 3/55 load-bearing, pinned
M2 drop .slice(0, count) plain-reader cap core jsonl survived 40/40 dead clause (guards already enforce ≤ count; base without slice is byte-identical on all 6 P cells) — completeness reporting, not a merge condition
M3 scannedLines > count off-by-one core jsonl killed 2/40 pinned
M4 && expected restore race re-inspection cli killed 2/29 pinned
M5 always recreated status mapping cli killed 1/29 pinned
M6 restore cause field options bag cli killed 2/29 pinned (incl. downstream consumer)

Positive control: unmutated suites green (231 + 29). No mutant regressed killed→survived relative to the PR's intent; the single survivor (M2) is dead code, not a coverage gap.

Targeted gates (witness 06-targeted-gates.png)

  • packages/core: jsonl-utils.test.ts + sessionService.test.ts + sessionService.corruption.test.ts231 passed (40/176/15; PR stated 226, actual 231).
  • packages/cli: both suites — 29 passed, matching the PR's claim.
  • tsc --noEmit for packages/core and packages/cli — clean.

Reviewer Test Plan walk-through

All five steps executable and verified: raced adoption without expected (E2/E6 + M4), status semantics (E1/E5), integrity budget (C1/C2 + S1), plain-reader compatibility (P1–P6 + M2), cause behavior (K1–K7 + M6). No unreachable step.

Findings

No blocking findings. Informational, non-blocking:

  1. Dead defensive clausereadLines's .slice(0, count) can never truncate: with budget='records' the outer line-loop guard and the inner per-record guard make results.length ≤ count unconditional. Proven by M2 surviving 40/40 and by base (no slice) producing byte-identical output on every plain-reader cell. The comment overstates what the slice does; the contract it names is enforced by the guards. Leave or delete — behavior is identical either way.
  2. Pre-existing semantic looseness, unchangedensureStandaloneDirectory(id, expected) with a fabricated expected for a session that never had a directory reports recreated after creating it (the expected identity is only validated when the directory exists). Identical on base and head; callers are trusted daemon internals and the next inspection surfaces the mismatch. Not introduced by this PR.

Not covered

  • Per-commit attribution: the checkout is depth-2; the metadata lists 4 commits but only the PR head is locally reachable (git rev-list HEAD^1..HEAD^2 = 1). The aggregate HEAD^1..HEAD diff was verified; per-commit claims were not individually exercised.
  • Non-Linux filesystem semantics (inode-less FAT/exFAT/SMB fallbacks) — container is Linux ext4/overlayfs only.
  • Whole-workspace build/typecheck — only the two affected workspaces were gated; the PR itself defers the workspace-wide signal to CI.
  • PR2A follow-ups: enforcement breadth, lock-key folding, directory race adoption, integrity budget, error serialization #9490 items 1–2 — explicitly out of the PR's scope.
  • The base control was compiled out-of-tree with the repo's TypeScript; the full-package base build shows 16–19 pre-existing type errors in unrelated files (dependency-resolution mismatches of this container, e.g. mime/lite, fdir, ignore, ajv) — none in the tested closures; emit was complete and every base module was runtime-smoke-verified before use.

Methodology

Ran in the CI verify container (node v22.23.2, merge-ref checkout). Head side = the CI-built packages/*/dist; base side = scratch git worktree at HEAD^1 compiled with the same tsc (core full-package emit into the worktree's dist, CLI as a 3-file closure conversation-workspace.ts + conversation-directory-identity.ts + session-id.ts), with the shared per-package node_modules linked in for the consumer harness. Harnesses are plain .mjs files in this directory that import each variant's compiled modules by absolute path and drive real filesystems (mkdtemp homes, chmod/rename races) and the real SessionService; the only instrumentation is at the environment seam (forcing the first inspection verdict, counting inspection calls), leaving the code under test unmodified. Control purity: grep '@qwen-code/' over the tested base closures returns nothing, and the PR leaves package.json/package-lock.json untouched, so both arms share identical external dependencies. Mutants were applied to the HEAD source one at a time via mutate.mjs, suites re-run, and the tree restored (git status --porcelain empty afterwards). Raw per-cell output in logs/; witnesses in evidence/ (01–06).

Flakiness gate log

rounds=5 files=3 skipped=0
file packages/cli/src/serve/conversations/conversation-workspace.test.ts: (cd packages/cli) npx --no-install vitest run ./src/serve/conversations/conversation-workspace.test.ts
file packages/cli/src/utils/conversation-directory-identity.test.ts: (cd packages/cli) npx --no-install vitest run ./src/utils/conversation-directory-identity.test.ts
file packages/core/src/utils/jsonl-utils.test.ts: (cd packages/core) npx --no-install vitest run ./src/utils/jsonl-utils.test.ts


per-file results (P=pass F=fail I=infra-exit, one letter per run):
  packages/cli/src/serve/conversations/conversation-workspace.test.ts: PPPPP
  packages/cli/src/utils/conversation-directory-identity.test.ts: PPPPP
  packages/core/src/utils/jsonl-utils.test.ts: PPPPP

verdict: pass
summary: 3 changed test file(s) x 5 identical rounds, no divergence

--- per-invocation detail (full copy in the artifact) ---
round 1 · packages/cli/src/serve/conversations/conversation-workspace.test.ts: P (exit 0)
round 1 · packages/cli/src/utils/conversation-directory-identity.test.ts: P (exit 0)
round 1 · packages/core/src/utils/jsonl-utils.test.ts: P (exit 0)
round 2 · packages/cli/src/serve/conversations/conversation-workspace.test.ts: P (exit 0)
round 2 · packages/cli/src/utils/conversation-directory-identity.test.ts: P (exit 0)
round 2 · packages/core/src/utils/jsonl-utils.test.ts: P (exit 0)
round 3 · packages/cli/src/serve/conversations/conversation-workspace.test.ts: P (exit 0)
round 3 · packages/cli/src/utils/conversation-directory-identity.test.ts: P (exit 0)
round 3 · packages/core/src/utils/jsonl-utils.test.ts: P (exit 0)
round 4 · packages/cli/src/serve/conversations/conversation-workspace.test.ts: P (exit 0)
round 4 · packages/cli/src/utils/conversation-directory-identity.test.ts: P (exit 0)
round 4 · packages/core/src/utils/jsonl-utils.test.ts: P (exit 0)
round 5 · packages/cli/src/serve/conversations/conversation-workspace.test.ts: P (exit 0)
round 5 · packages/cli/src/utils/conversation-directory-identity.test.ts: P (exit 0)
round 5 · packages/core/src/utils/jsonl-utils.test.ts: P (exit 0)

Evidence images

01-jsonl-ab-base-vs-head

02-ensure-ab-base-vs-head

03-cause-ab-base-vs-head

04-consumer-ab-base-vs-head

05-mutation-matrix

06-targeted-gates

Harness scripts and raw logs are in the workflow run artifacts (7-day retention).

Qwen Code · sandboxed verification

@qwen-code-ci-bot

Copy link
Copy Markdown
Collaborator

Thanks for the follow-up!

Template looks good ✓

Problem: observed, not theoretical. All three defects were reported by @wenshao during the review of #9341 and are tracked in #9490 (items 3, 4, 5) with concrete mechanisms. One of them already touches production: readLinesWithIntegrity gates the session-head creation-metadata reads in sessionService.

Direction: aligned. This is review-directed follow-up on the standalone isolation primitives merged in #9341, and #9490 explicitly scopes these items as the remaining work. The integrity reader serves session-head classification today; the directory primitives feed PR2B.

Size: core path touched (packages/core/src/utils/jsonl-utils.ts). Production logic 64 lines (36 core, 28 CLI serve/utils) vs. 124 test lines and 2 docs lines — far below any escalation threshold. The Tier 2 consumer check is done in Stage 2: every downstream consumer is named and verified there.

Approach: minimal — each fix is exactly what #9490 prescribes, the diff carries nothing unrelated, and the plan doc is updated to match the new created/recreated contract.

Risk: no elevated-risk paths matched.

Moving on to code review. 🔍

中文说明

感谢这个后续 PR!

模板完整 ✓

问题:已观测到,不是理论问题。三个缺陷都是 @wenshao#9341 评审中报告、由 #9490(item 3、4、5)追踪的,机制描述具体。其中一项已影响生产路径:readLinesWithIntegritysessionService 的会话头部创建元数据读取把关。

方向:对齐。这是 #9341 合并后、由评审驱动的后续修复,#9490 明确将这些项列为遗留工作。完整性读取器今天就服务于会话头部归类;目录原语将在 PR2B 中使用。

规模:触及核心路径(packages/core/src/utils/jsonl-utils.ts)。生产逻辑 64 行(core 36 行,CLI serve/utils 28 行),测试 124 行,文档 2 行——远低于任何升级阈值。Tier 2 的消费者核查在 Stage 2 完成:已点名并验证所有下游消费者。

方案:最小化——每个修复都严格对应 #9490 的处方,diff 不含无关改动,计划文档也同步更新为新的 created/recreated 契约。

风险:未命中高风险路径。

进入代码审查 🔍

Qwen Code · qwen3.8-max

Reviewed at db27aea2b6a49084f514b6c5cfee0b4e3a1f9e91 · re-run with @qwen-code /triage

@qwen-code-ci-bot

qwen-code-ci-bot commented Aug 20, 2026

Copy link
Copy Markdown
Collaborator

Code review

No blockers. All three fixes do exactly what #9490 prescribed, and I verified the downstream consumers rather than taking the description at face value:

Raced directory adoption (item 3). The re-inspection in ensureStandaloneDirectory is now unconditional — a directory that appears in the race window is adopted only through the same inspection verdict as one found up front, and if it vanished again before the re-inspection the method fails closed with identity_changedcompromised. The created/recreated split keys off whether an identity was captured beforehand, matching the documented contract. I grepped for consumers of the ensure-result union: tests only, none in production yet, so the new created member cannot break anything.

Integrity vs. record budget (item 4). The shared internal reader gains a budget mode: readLinesWithIntegrity now counts physical non-empty lines, so its fail-closed verdict always covers a fixed file prefix instead of one that shrinks around early glued records; readLines keeps its record-budget contract (at most count records). I checked the integrity reader's single production consumer, sessionService.readCreationMetadataInternal, and it is safe under the widened record window: it branches on complete and treats the records purely as a scan window for first-wins metadata extraction, with no count assumption.

Error cause (item 5). Passing cause through the native ES2022 options bag is the textbook fix: no cause key when none was given, a non-enumerable own property when one was. The value-based read in liveIdentityError keeps working, and the repo's es2022 target makes the options bag type-check.

The added and updated tests pin the changed behaviors — traced against the pre-PR logic, the raced-adoption, line-budget, and cause-enumerability tests all fail without the fix — and a compatibility test pins that the plain reader's record-budget contract survives. The updated stream-cleanup expectation is a consequence of the budget change, not an unrelated edit.

CI evidence (the PR's own checks on the reviewed commit)

No failures. The main unit suite and Serve A/B are still running on this head; everything completed so far is green. The cancelled triage check is the prior triage run superseded by this one, not a PR defect. An earlier Serve A/B pass against the pre-force-push head (240a196) found no response changes across 4 scenarios; the A/B run against the current head is the in-progress one.

Final CI results for db27aea (auto-updated by the triage finalize job after CI completed):

Check Conclusion
Classify PR ✅ success
Dependency CVE audit ✅ success
Desktop Shell (ubuntu-22.04) ✅ success
Desktop Shell (windows-2022) ✅ success
Live Host (macos-latest) ✅ success
macos-latest / Java 21 ✅ success
Real daemon E2E / Java 11 ✅ success
Secret scan (TruffleHog) ✅ success
Serve A/B (ubuntu-latest, Node 22.x) ✅ success
Test (ubuntu-latest, Node 22.x) ✅ success
ubuntu-latest / Java 11 ✅ success
ubuntu-latest / Java 17 ✅ success
ubuntu-latest / Java 21 ✅ success
web-shell E2E Smoke (ubuntu-latest, Node 22.x) ✅ success
windows-latest / Java 21 ✅ success

One row per check name (latest run); skipped checks omitted; failures sort first. / 每个检查名一行(取最新一次运行),省略 skipped,失败项排在最前。

Verification gaps

Not verified: the author's local runs were macOS-only unit tests (their claim, not re-run here); the CI matrix above is the cross-platform signal, and the unit suite for this head is still in flight. Sandboxed verification would settle the behavioural claim end-to-end: @qwen-code /verify — that raced adoption fails closed and the integrity verdict covers a fixed line prefix on this head vs. base, which the pending unit suite pins only via targeted (partly mocked) tests.

Real-scenario (tmux) testing: N/A — internal serve primitives with no user-visible surface, and this is an unattended run, so no PR code is executed here regardless.

中文说明

代码审查

无阻塞问题。三处修复都严格对应 #9490 的处方,且我逐一核实了下游消费者,而不是只看描述:

竞态目录收养(item 3)。 ensureStandaloneDirectory 的重新检查现在是无条件的——竞态窗口内出现的目录只有通过与"初始即存在"相同的检查裁决才会被采纳;若在重检前它又消失了,则以 identity_changedcompromised fail-closed。created/recreated 以是否事先捕获了身份来区分,与文档契约一致。我检索了 ensure 结果并集的生产消费者:目前只有测试,新增的 created 成员不会破坏任何东西。

完整性预算 vs 记录预算(item 4)。 共享的内部读取器新增 budget 模式:readLinesWithIntegrity 按物理非空行计数,fail-closed 裁决因此始终覆盖固定文件前缀,而不再随早期的粘连记录收缩;readLines 保持记录预算契约(至多 count 条记录)。我检查了完整性读取器唯一的生产消费者 sessionService.readCreationMetadataInternal,在放宽的记录窗口下是安全的:它只看 complete,并把记录纯粹当作"首个命中生效"元数据提取的扫描窗口,没有任何数量假设。

错误 cause(item 5)。 通过 ES2022 原生 options bag 传递 cause 是标准修法:未提供 cause 时不存在 cause 键,提供时则为不可枚举自有属性。liveIdentityError 的按值读取仍然可用,仓库 es2022 target 也保证类型检查通过。

新增与更新的测试钉住了行为变化——对照 PR 前逻辑逐条推演,竞态收养、按行预算、cause 可枚举性测试在缺少修复时都会失败——另有一个兼容性测试钉住普通读取器的记录预算契约不变。流清理测试期望值的更新是预算变化的结果,不是无关改动。

CI 证据(该 commit 上 PR 自己的检查)

无失败。主单元测试与 Serve A/B 仍在运行;已完成的全部为绿。cancelled 的 triage 是被本次运行取代的上一轮 triage,不是 PR 缺陷。针对 force-push 前 head(240a196)的 Serve A/B 曾在 4 个场景下无响应差异;当前 head 的 A/B 正在进行中。

(CI 表格见上方机器可读区域)

验证缺口

未验证:作者本地仅跑了 macOS 单元测试(作者自述,未在此重跑);跨平台信号看 CI 矩阵,本 head 的单元测试仍在进行中。沙箱验证可以端到端坐实行为声明:@qwen-code /verify —— 竞态收养是否 fail-closed、完整性裁决是否覆盖固定行前缀,目前仅由(部分 mock 的)定向测试钉住。

真实场景(tmux)测试:N/A —— 内部 serve 原语,无用户可见界面;且本次为无人值守运行,无论如何都不会执行 PR 代码。

Qwen Code · qwen3.8-max

Reviewed at db27aea2b6a49084f514b6c5cfee0b4e3a1f9e91 · re-run with @qwen-code /triage

@qwen-code-ci-bot

Copy link
Copy Markdown
Collaborator

Confidence: 4/5 — every stage checks out and the fixes are exactly the prescribed ones; withholding the last point only because this head's unit suite and Serve A/B are still in flight.

This is what follow-up work should look like: three review findings from #9341, each fixed with the minimal prescribed change, each pinned by a test that fails without the fix, and nothing else in the diff. Before believing the "no production consumers" claim I went looking for downstream damage — it holds for the directory primitives, and the integrity reader's single consumer (readCreationMetadataInternal) is genuinely safe under the widened record window, not just described as safe. That window change is the one thing future PR2B consumers should inherit awareness of: readLinesWithIntegrity can return more records than count — documented in the JSDoc, fine today.

Independent-proposal check: my own plan for items 3/4/5 (unconditional re-inspection plus the created/recreated split, a budget mode on the shared reader, the ES2022 options bag) matches what landed — no simpler path missed.

Verdict: approve, deferred until CI lands green on this commit — the approval instruction travels with the marker below, and the finalize job will withhold it if anything lands red or the head moves.

中文说明

置信度:4/5 —— 各阶段均通过,修复也严格对应处方;扣掉的一分只因为该 head 的单元测试与 Serve A/B 尚未出结果。

这是后续修复应有的样子:#9341 的三个评审问题,每一个都用最小处方修复,每一个都有"缺少修复就会失败"的测试钉住,diff 里没有别的东西。在相信"没有生产消费者"之前我先去找了下游影响——目录原语确实如此,完整性读取器唯一的生产消费者(readCreationMetadataInternal)在放宽的记录窗口下也确实安全,而不只是被描述为安全。这个窗口变化是未来 PR2B 消费者应当知道的一点:readLinesWithIntegrity 可能返回多于 count 条记录——JSDoc 已写明,目前无碍。

独立方案对照:我对 item 3/4/5 的设想(无条件重检加 created/recreated 拆分、共享读取器加预算模式、ES2022 options bag)与落地的实现一致——没有遗漏更简单的路径。

结论:批准,但等该 commit 的 CI 变绿后生效——批准指令随下方标记发出;若有检查变红或 head 移动,finalize 任务会撤回批准。

Qwen Code · qwen3.8-max

Reviewed at db27aea2b6a49084f514b6c5cfee0b4e3a1f9e91 · re-run with @qwen-code /triage

@qwen-code-ci-bot

Copy link
Copy Markdown
Collaborator

Triage re-run completed without a new review.

⚠️ The bot has neither a verdict nor a deferral on db27aea2b6a49084f514b6c5cfee0b4e3a1f9e91 — no APPROVED, CHANGES_REQUESTED, or COMMENTED review of its own. A DISMISSED one does not count: dismiss_stale_reviews voids the bot's approval on every push, which is exactly when a fresh one is needed. If this re-run was meant to review or approve, it did not, and an approval left by another account is a separate vote that does not count as the bot's own.

⚠️ 机器人在 db27aea2b6a49084f514b6c5cfee0b4e3a1f9e91既没有裁决也没有 defer —— 没有属于它自己的 APPROVEDCHANGES_REQUESTEDCOMMENTED 评审。DISMISSED 不算:dismiss_stale_reviews 会在每次推送时作废机器人的批准,而那恰恰是需要一次新批准的时刻。如果这次重跑本应评审或批准,那么它没有做到;而其他账号留下的批准是另一张票,不能算作机器人自己的。

The stage comments above were updated with the latest result. View workflow run.

上方各阶段评论已更新为最新结果。查看工作流运行

@qwen-code-ci-bot qwen-code-ci-bot left a comment

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.

LGTM, looks ready to ship — CI landed green after the review. ✅

@yiliang114 yiliang114 left a comment

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.

LGTM. CI is green and the change is a focused followup that closes the leftover items from the standalone-session review:

  • ensureStandaloneDirectory now reports created (first-ever, no expected) vs recreated (re-created after a captured identity vanished), and re-inspects the race window even without an expected identity instead of blindly adopting a raced creator's directory.
  • readLinesWithIntegrity measures completeness against a line budget (not a record budget) so fail-closed coverage does not shrink on }{-glued lines; readLines keeps its record-budget contract via .slice(0, count).
  • ConversationDirectoryIdentityError carries cause as a non-enumerable property only when provided, via the Error options bag.

Tests cover each semantic change.

@doudouOUC
doudouOUC added this pull request to the merge queue Aug 20, 2026
Merged via the queue into QwenLM:main with commit 2e6151a Aug 20, 2026
110 of 112 checks passed
@doudouOUC
doudouOUC deleted the fix/standalone-pr2a-followups branch August 20, 2026 08:10
@qwen-code-ci-bot

Copy link
Copy Markdown
Collaborator

Released in v0.21.15.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants