Repository navigation
[PR] fix(#4106): tag cleanup is now idempotent — one call reaches the fixed point - #4148
ranxianglei wants to merge 2 commits into
Conversation
📦 Built Package ArtifactBranch: Option A — Install this exact build (recommended)npm install -g billion-context@0.0.4944-pr.4148.d688bcbThis exact version is immutable and reproducible. The rolling Option B — Download artifact
tar xzf billion-context-pr4148.tgz
npm install -g packageInstall instructions are refreshed on each push. |
(ai: vllm-qwen/qwen3.8-27b) 查重:不重复。 #4106 是唯一的幂等性缺陷报告;#4107(流式分块边界依赖)是同家族但机制不同——那是流式状态机的 chunk-split 问题,本 PR 明确不碰、且其回归钉(C#4107/C#4107-2)在本分支上保持原样绿着。open+closed 里检索 idempoten/stripAcpTags 无其他并行修复。 标签: bug + p2 + scope/wiring(修的是协议接线层 wire 文本清理的确定性缺陷;严重度与原 issue #4106 一致——已发送字节不稳定但无数据丢失)。 独立审核结论(未照抄 PR 描述,以下均为本轮重新验证): 结构化八问
行为变更披露(old→new,PR 已声明、我复核成立):
验证证据:
结论:方向正确,机制打到根因(门控时机而非症状遮蔽),回归审计通过,唯一审核发现(docstring 过强声明 + cap 角落缺 pin)已在 |
… + fixed-point walk The DEAD_REF_CLOSE gate (`out !== text`) was evaluated mid-pipeline, before the marker-line / truncate / bili-artifact channels ran, so evidence produced by those later channels never counted in the same call: a forged marker line stripped at the end of call 1 left trailing dead-ref closes standing, and a second cleanup call shrank them via the end-anchored TRUNC passes. The verdict depended on prior cleanup state, not on content — retry/rebuild paths that re-normalize already-cleaned messages shipped different bytes each pass. Two-part mechanism fix in src/loop/tag-echo-filter.ts: 1. Input-side attestation: the gate now also counts shapes produced by channels that run after it (truncated open at EOF, forged marker line, bili chain carrier / header block), computed once on the ORIGINAL text. Close-side channels (TRUNC_CLOSE / TRUNC_REF_CLOSE) deliberately do not self-attest — they are the same close-only family the gate protects (#1760/#644 content preservation, bare-ref pins unchanged). 2. Bounded fixed-point walk: stripAcpTags applies the pipeline until stable (every non-stable pass strictly shortens; cap bounds adversarial cascade depth), so one call does everything repeat calls would.
…-point guarantee in the docstring (review finding) Review of the idempotence fix found the docstring overclaimed: STRIP_PASS_CAP=8 bounds adversarial cascade depth, so an input layering more than 8 cross-stage exposures (e.g. 9 stacked $-anchored truncated closes that peel one per pass without ever attesting) keeps its residue at the cap, and a repeated call peels further. Empirically verified: K<=8 converges in one call, K=9 leaves 'A</acpa\n' and f(f(x)) != f(x). The behavior is the intended CPU-bound tradeoff (strictly better than master, which was non-idempotent from depth 2); this commit only makes it honest: docstring now states the bounded guarantee, and a pin test documents the corner in both directions.
213a7e1 to
48cefe7
Compare
(ai: vllm-qwen/qwen3.8-27b) Status update — CI now fully green, PR is merge-ready.
Merge is yours to do — I can't merge PRs. #4148 |
Fixes #4106
What was broken
stripAcpTagswas not idempotent: theDEAD_REF_CLOSEgate (out !== text) was evaluated mid-pipeline, BEFORE the marker-line / truncate / bili-artifact channels ran. So evidence produced by those later channels never counted in the same call — a forged marker line stripped at the end of call 1 left trailing dead-ref closes (m3117</cPa) standing, and a second cleanup call shrank them via the ungated end-anchored TRUNC passes. The verdict depended on what prior cleanup had already changed, not on the content. Retry/rebuild paths that re-normalize already-cleaned messages (the #3938 PR-D alignment judge usesnormalizeEchoArtifacts) shipped different bytes per pass (#3837 family).Fix (src/loop/tag-echo-filter.ts)
Two-part mechanism fix:
attested || out !== text, whereattestedis computed ONCE on the original text from the channels that run AFTER the gate: truncated open at EOF, forged marker line, bili chain carrier / header block. Close-side channels (TRUNC_CLOSE / TRUNC_REF_CLOSE) deliberately do NOT self-attest — they are the same close-only family the gate protects ([bug] 单行中文答案被误判为退化轮:marker 行首前缀按设计被扣住,终点释放却被计成 markup(chat-completions 线整轮报错) #1760/[bug] #361 residual, form 3: turn whose entire text is a render-tag echo strips to an EMPTY message — agent records a marker-only turn, stalls until manual nudge (v0.1.92, post-#365) #644), so unattested close-only shapes stay byte-identical to before.stripAcpTagsapplies the pipeline until stable (every non-stable pass strictly shortens the text, so it terminates;STRIP_PASS_CAP = 8bounds adversarial cascade depth on attacker-shaped fields). This closes cross-stage exposure holes no single-pass rearrangement covers — e.g. a trailing-close strip exposing a truncated open or a marker line at the NEW boundary.Behavior changes (old → new)
stripAcpTags("ref m00001 </acp") → "ref m00001 "(bare ref kept), unattested mid-text byte-identical — all [bug] 单行中文答案被误判为退化轮:marker 行首前缀按设计被扣住,终点释放却被计成 markup(chat-completions 线整轮报错) #1760/[bug] #361 residual, form 3: turn whose entire text is a render-tag echo strips to an EMPTY message — agent records a marker-only turn, stalls until manual nudge (v0.1.92, post-#365) #644/[bug] tag-echo 在 0.1.194 仍放行一条 #2190 形态残渣(mNNNNN</acp 内联在正文行首,#2229 治理后复现) #2862 pins green.Tests
tests/issue4106-strip-idempotency.test.ts: issue repro (stable after one call), attestation-in-same-call, content-preservation guards ([bug] 单行中文答案被误判为退化轮:marker 行首前缀按设计被扣住,终点释放却被计成 markup(chat-completions 线整轮报错) #1760/[bug] #361 residual, form 3: turn whose entire text is a render-tag echo strips to an EMPTY message — agent records a marker-only turn, stalls until manual nudge (v0.1.92, post-#365) #644), [bug] tag-echo 在 0.1.194 仍放行一条 #2190 形态残渣(mNNNNN</acp 内联在正文行首,#2229 治理后复现) #2862 attested-EOF pin, cross-stage cascade pins, seeded fragment-soup fuzz assertingf(f(x)) === f(x)for bothstripAcpTagsandnormalizeEchoArtifacts.tests/verif-tag-echo-filter.test.ts: C#4106 pin FLIPPED to assert idempotence (as the pin itself instructed); the P2 domain exclusion removed — the full random domain (marker lines + trailing dead-ref-closes included) now asserts idempotence under seed 0x7ea4c0de.