Skip to content

[PR] fix(#3013,#3022): refusal is terminal — bili_final marker + storm-visible logging (A1+A3) - #3045

Closed
ranxianglei wants to merge 8 commits into
masterfrom
fix/dsh-refusal-terminal-signal
Closed

ranxianglei wants to merge 8 commits into
masterfrom
fix/dsh-refusal-terminal-signal

Conversation

@ranxianglei

Copy link
Copy Markdown
Owner

Closes #3013 (logging half + wire contract), refs #3022, #3016, #2490, #3004.

什么问题

#3013 现场:一个会话烧了 5069 次被拒的 dsh 原生压缩调用(首拒后 5038 次重试,中位间隔 328ms,上游 0 次到达),用户全程只看到一个卡住的"生成中"。两半问题:

  1. 拒绝对 dsh 不是终态:refusal body 只有 retryable:false。dsh 的重试压力来自它自己的 raw ledger(dsh压缩与bili压缩冲突 #2490),bili 侧任何东西都缩不小它 → 同一个调用永远以同样方式失败,客户端却没有可锁存的机读终态标记。
  2. 风暴不可见:pipeline 首拒 once-warn 之后全部静默(5051 条后续拒绝 0 log 行);passthrough lane 反方向 — 每次拒绝都 warn(风暴时刷屏)。

修复(A1 + A3,owner 在 #3016 讨论中定的两件)

A1 — 拒绝即终态

A3 — 日志

为什么不是让拒绝可重试

owner 裁决(#3016):"最新的bili就是100%以顺位为主的…任何破坏顺序的都应该被拒绝"。放行 dsh 原生压缩 = 破坏顺序(落地的 checkpoint 永久遮蔽原始历史,不可逆);拒绝的成本全部在 dsh 侧、可见、可恢复。终态化 + 讲清楚出路才是与裁决一致的方向。

测试

tests/issue3013-refusal-terminal.test.ts(3 tests):

Red-green 证明:stash 两个 src 文件 → 新测试全红;恢复 → 绿。

回归(97 tests 绿):dsh-compaction-guard、issue2028 toggle、issue2709 两条、issue2658、issue2432、issue2451、issue2612 claude guard、issue2612 draft-tail、codex-local-compaction、compaction-archive、compaction-trigger-finality。tsc clean。

影响面

  • 仅 dsh 原生压缩被拒路径;正常请求、放行路径(allowDshCompaction)字节不变。
  • 拒绝体新增字段是加法(现有消费方按 error.code/status 识别,不受影响)。
  • 日志行为变化即本 PR 目的;容量上 1/100 节奏 ≈ 风暴时每小时 ~11k 次拒绝才 110 行(此前为 0 行或 11k 行,两个坏极端)。

@github-actions

github-actions Bot commented Oct 13, 2026 •

Copy link
Copy Markdown
Contributor

📦 Built Package Artifact

Branch: fix/dsh-refusal-terminal-signal (9d9d23c)

Option A — Install this exact build (recommended)

npm install -g [email protected]

This exact version is immutable and reproducible. The rolling pr tag tracks the newest test build across all PRs.

Option B — Download artifact

  1. Download the artifact from the Actions run
  2. Extract the tarball and install:
tar xzf billion-context-pr3045.tgz
npm install -g package

Install instructions are refreshed on each push.

@ranxianglei

Copy link
Copy Markdown
Owner Author

🤖 Powered by ework · qwen3.8-27b

[bot] 🏷 收到。这是 [PR] issue(PR #3045,分支 ranxianglei:fix/dsh-refusal-terminal-signal)。按规范:不重做,改为拉取分支核对代码、确认 CI 状态、出结构化八问结论。审查进行中,稍后回帖。

…ible logging (A1+A3)

Field report #3013: one session burned 5069 refused dsh compaction calls
(5038 retries, median 328ms, 0 outbound) while the user watched a stuck
'生成中'. Two halves:

A1 — the refusal body now carries bili_final:true (top level on both
protocol shapes; inside error on the openai envelope where retryable
lives). retryable:false did not stop the loop: dsh's retry pressure comes
from its own raw ledger (#2490), which nothing on bili's side can shrink,
so the same call fails identically forever. bili_final is the stable
machine-readable contract for dsh upstream to latch (consumption is a
cross-project follow-up). REFUSAL_MESSAGE now states the terminal
semantics and the levers (bili compress / #2474 recipe / #2028 opt-in;
dropped the stale web-UI mention deleted by #3042).

A3 — logging. Pipeline lane: after the once-warn, every 100th refusal
logs an info with the running count, the #2490 mechanism, and the levers
(previously: 5051 silent refusals, zero log lines, user blind).
Passthrough lane: was warn-per-refusal (the spam direction) — now warns
on the process's first refusal, then the same 1-per-100 cadence.

Tests: tests/issue3013-refusal-terminal.test.ts (A1 body shapes on both
protocols incl. error.bili_final; A3 storm cadence on both lanes via
setLogCapture with log:true rigs — request-scoped log is gated on
opts.log, log:false rigs made guard lines no-ops). Red-green proven.
Family regressions green (dsh guard/2028/2709/2658/2432/2451, claude
guard #2612, codex 2451, archives): 97 tests.
@ranxianglei
ranxianglei force-pushed the fix/dsh-refusal-terminal-signal branch from aa9779f to 34b6c7e Compare October 13, 2026 13:27
@ranxianglei

Copy link
Copy Markdown
Owner Author

🤖 Powered by ework · qwen3.8-27b

[bot] 🏷 PR #3045 审查结论(分支 ranxianglei:fix/dsh-refusal-terminal-signal,head 7400867,3 文件 +230/−4)

按 [PR] issue 规范:不重做,拉分支逐行核对代码 + 确认 CI。结构化八问如下。

① 是什么问题
dsh 原生压缩被 bili 拒绝后,客户端没有机读的"终态"标记可锁存,会无限重试同一个注定失败的调用——现场一个会话烧了 5069 次拒绝重试(首拒后 5038 次、中位间隔 328ms、上游 0 到达),用户全程只看到卡住的"生成中";同时日志两头都错:pipeline 首拒告警后全静默、passthrough 反向每次刷屏,风暴完全不可见。

② 什么原因导致的
根因不是"重试太多",是两件事叠加:(1) 拒绝体只有 retryable:false,而 dsh 的重试压力来自它自己的 raw ledger(#2490),bili 侧任何东西都缩不小它 → 同一调用永远同样失败,但客户端没有稳定的终态标记能锁存停止;(2) 日志策略反向——pipeline 首拒后静默、passthrough 每次 warn。修的是这两个机制本身(疏),不是去堵重试或过滤字节。

③ 影响范围与严重程度
仅"dsh 原生压缩被拒"路径的正常会话(触发条件:dsh auto-compaction 开着且未 allowDshCompaction)。严重度=特定条件下体验受损:会话卡住无提示+日志无信号;不丢数据、不影响放行路径和正常请求。

④ 怎么修复的
A1 把拒绝定性为终态并固定为契约:两种协议 shape 的拒绝体顶层都加 bili_final:true(openai error 信封里也加——retryable 所在处),REFUSAL_MESSAGE 重写讲清 terminal 语义+三个出路(bili compress / #2474 关 dsh auto-compaction / #2028 allowDshCompaction),顺带删掉已失效的"(web UI)"(#3042 已删该开关,base 已含)。A3 让风暴可见:pipeline 首拒之后每第 100 次记一条 info(累计计数+raw-ledger 高水位+#2490 机制+出路+"nudge 通道可能死了就报告");passthrough 从每次 warn 改为进程首次 warn+同 1/100 节奏。常量 DSH_REFUSAL_STORM_LOG_EVERY=100 硬编码不进配置(#2155 先例)。

⑤ 是否有回归问题 —— 审过的既有行为逐项:

  • 拒绝体加字段是纯加法:现有消费方按 status/error.code 识别,不受影响。dshCompactionRefusal 仅 handle.ts 两处调用,都是原样把 status+body 作为本地 422 响应回给客户端,不转发给上游(非上游 wire),无 wire-fidelity 问题。
  • REFUSAL_MESSAGE 文案变化:删"(web UI)"→"config file or env"更准确而非回归;关键标识符(在bili网页配置界面支持关闭和开启dsh的自动压缩功能 #2028 / allowDshCompaction / BILI_ALLOW_DSH_COMPACTION)全保留。
  • 日志行为变化即本 PR 目的(影响面已披露 old→new):passthrough"每次 warn"→"首拒 warn+1/100 info"(减刷屏、首次信号保留);pipeline"首拒后静默"→"+1/100 info"(增可见性)。两个方向都是有意变更。
  • 双压缩模式(§6):guard 在 processTurn 之前的 pre-loop 门,plugin/proxy 共用同一 guard,改动不触碰 summary carrier/injection → 无双模式回归。
  • 高危模式逐项过:无语义漂移、无 fail-fast 变吞错(仍 422 fail-fast)、无阈值/时序漂移(新增内部常量,非既有阈值改动)、输出格式仅加字段(加法)。
  • 正常请求与放行路径(allowDshCompaction):门 opts.allowDshCompaction !== true 不变,字节不变。
  • 测试钉住的关键不变量:上游隔离(captured.length===0)、once-warn 恰好 1 条、storm info 恰好在 fix(server): resolve backpressure wait on client disconnect so streams can't hang the session lock #100、passthrough ≤2 行/100 次。

⑥ 是否破坏缓存 —— 不涉及。拒绝发生在压缩/缓存之前,不改 prefix-cache、增量游标、去重键。

⑦ 是否改了设计方向 —— 不涉及架构转向。是把"拒绝"从"瞬时错误"重新定性为"终态决策"并固定为稳定契约(bili_final),与 owner 在 #3016 的裁决一致(任何破坏顺序的都该被拒绝,拒绝成本全在 dsh 侧、可见、可恢复)。消费端(dsh 上游锁存 bili_final)是跨项目跟进,bili 侧契约在此固定。

⑧ 是否引入了新的配置 —— 无新 env/config/CLI flag。DSH_REFUSAL_STORM_LOG_EVERY=100 是硬编码内部常量(明确不进配置,#2155 先例),config-surface-guard 已过绿。

CI / 合并判定

  • 30+ 门禁全绿:static-gates×4、config-surface/kernel/pi-subagents/lane/version guard、全部 dsh/codex/opencode/pi e2e lane、hermetic registry e2e、docs-sync/coverage、build-artifact。
  • 仅剩核心 test 套件(ubuntu/win × node22/24 共 4 组合)在跑,尚无失败。基于逐行审查+全绿门禁+dsh-lane e2e 已绿,判定可合;等最后一个全量 test 作业转绿即可收工,我会在其落地后回帖确认。
  • 分支落后 master 2 个提交,但那两个提交只动 src/responses-ws.ts+其测试,与本 PR 三个文件零重叠(不同子系统)→ 无冲突、无语义交互,可直接合或先 rebase 均可。

小问题处理:审查未发现需顺手修的小缺陷(diff 干净、注释与代码事实一致)。

@ranxianglei

Copy link
Copy Markdown
Owner Author

🤖 Powered by ework · qwen3.8-27b

[bot] 🏷 CI 全绿,收工确认。 head 7400867 上全部 check-run 转绿:核心 test 套件在 ubuntu/win × node22/24 四个组合全部通过,加上此前已绿的 static-gates、config-surface/kernel/pi-subagents/lane/version guard、全部 dsh/codex/opencode/pi e2e lane、hermetic registry e2e、docs-sync/coverage、build-artifact——无一失败。

最终判定:可合。代码逐行核对与 PR 描述完全一致(A1 三种 body shape 的 bili_final 位置、A3 两条 lane 的 1/100 节奏与 once-warn 落点都对得上),测试有真实牙齿(上游隔离、once-warn 恰好 1 条、storm info 恰在 #100、passthrough ≤2 行/100 次),无回归、无 wire-fidelity / 双压缩模式 / 缓存问题,未引入新配置。分支落后 master 的 2 个提交只动 responses-ws.ts,与本 PR 零重叠,可直接合或先 rebase。合并留给你操作。

# Conflicts:
#	src/server/dsh-compaction-guard.ts
…sationOrder adapter

The fa7dc4e merge kept #2731's direct personaNamespace(...) call for the
google lane but dropped its import — master had already swapped the direct
calls for the session-resolve.ts personaConversationOrder wiring adapter
(content-blind, keyed by order+id, (systemText, log) params gone). Translate
the call faithfully and add the empty-system non-anchoring guard the other
lanes already have, so typecheck (tsconfig.json, not CI-run) is green again.
Verified: typecheck 0; dsh-persona-fingerprint + side-request-isolation +
issue2709 + dsh-compaction-guard 58/58.
@ranxianglei ranxianglei added the p1 label Oct 13, 2026
…the side-lane suite

The persona-gate conjunct sits on the sideRequestLike line in handle.ts, so
lane-guard (#2170) demands the suite edit. Pins: a dsh MAIN google request
with NO systemInstruction rides the verbatim key and never enters
personaConversationOrder (whose cold-shallow branch would fork |sub:aux).
25/25 in side-request-isolation.
… copy

Master-red picked up in this branch: 98e4cf4 (#3008) rewrote the codex
remediation line (BILI_CODEX_COMPACT only governs explicit compaction
requests) without touching the pin — fails on pure origin/master too.
Verified locally before/after: 10 pass 1 fail -> 11 pass.
# Conflicts:
#	tests/conflict-remediation.test.ts
… + golden pins

Master-red picked up in this branch (fails on pure origin/master too):
- ca93ef4 (#3058) moved the kernel default without touching the two #1387
  tail tests (which ride the default and rely on m00003–m00007 being inside
  the protected zone) or the storage-render goldens (persisted metadata
  section digest is the only drift; state/blockContents/stats unchanged).
- #1387 tests now pin preserveRecentMessages: 5 explicitly — they exercise
  the stop-signal contract, not the preserveRecent default.
- goldens regenerated via scripts/update-storage-render-goldens.ts.

Verified: compress-success-tail 8/8, golden-storage-render 5/5 (was 2/6).
@ranxianglei

Copy link
Copy Markdown
Owner Author

(ai: zhipuai-lb/glm-5.3)

已并入 #4590。关单收尾;误判请 reopen。

@ranxianglei ranxianglei changed the title fix(#3013,#3022): refusal is terminal — bili_final marker + storm-visible logging (A1+A3) [PR] fix(#3013,#3022): refusal is terminal — bili_final marker + storm-visible logging (A1+A3) Oct 24, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

1 participant