Skip to content

[bug] no-visible-context guard is bypassed for ranges overlapping active blocks, marking refs DEAD #3859

Closed

Description

@yanhuojiyusheng

Summary

In billion-context 0.1.201, the noVisibleContextRefusal guard can be bypassed when the submitted range overlaps an active block span. With an empty message view and persisted refs, the proxy then falls through to applyCompression, which treats the absent messages as unanchorable and permanently records otherwise-live refs as dead.

This appears to be a gap in the #3126 fix: its no-view refusal only applies when the requested range does not touch an active block span.

Evidence

From a Pi session on Linux x64, Node v24.20.0, bili v0.1.201:

  • 14:43:16: compression succeeded for six ranges (~269,455 tokens saved), with 212 messages in view.
  • 14:49:09: client disconnected mid-stream; the same session was evicted and immediately cold-loaded with 872 refs and 15 active blocks.
  • The next compress call logged ctx has 0 message(s), state has 872 ref(s) mapped and requested m00699–m00721, m00774–m00860, m00869–m00870.
  • Instead of the no-visible-context refusal, it returned Requested range(s) cannot be anchored and said those refs were recorded as DEAD.

The installed dist/agent/pi-native.js implements the guard by returning null if any submitted range overlaps activeBlockSpans, even when view.length === 0. It then calls ctx.core.applyCompression({ranges,messages:ctx.compressMessages??ctx.messages,...}) with an empty message list. The kernel cannot anchor refs without messages and marks them dead.

Expected

If the view is empty while refs remain mapped, every range should be refused as no-visible-context without changing state or adding dead refs, regardless of overlap with active blocks. Retry should only be suggested once a new model request restores the message view.

Actual

Ranges overlapping active blocks bypass the guard, reach compression with an empty message list, and are permanently marked dead. This matches the error's #3126-like symptom even though #3126 is closed.

Questions

Could the guard be unconditional whenever the message view is empty and mapped refs exist, or could the kernel distinguish unavailable input messages from genuinely dead refs before writing tombstones?

Activity

  1. ranxianglei commented on Oct 19, 2026

    @ranxianglei
    Owner

    🤖 Powered by ework · qwen3.8-27b

    (ai: vllm-qwen/qwen3.8-27b)

    查重: 不重复——按失败签名(空视图 + 压缩被拒 + refs 记 DEAD)检索了 open+closed issue 与近期 PR,无同机制条目。#3126 已关闭,本条是它的守卫缺口(issue 自身也指出了);#2362 是内核侧墓碑机制的来源,不是同一问题。

    标签: bug / pi / scope/kernel / p2(缺陷;Pi 插件车道触发;上下文管理守卫;p2——特定条件(会话被逐出后、下一个模型请求前发起触碰活跃块的压缩)下压缩失败并误报 DEAD,墓碑在下一轮模型请求时自愈,非永久数据丢失)。

    审核结论(复现 / 层次 / 方案)

    已在 v0.1.201 代码树上完整核对了你日志对应的代码路径,机制确认成立:

    1. 守卫的豁免条件与内核的墓碑条件不匹配(根因)。src/stream.ts 的 no-visible-context 守卫在「视图为空 + 有映射 refs」时,只要任一请求区间跨度上重叠任一活跃块就放行(fix: isolate side requests (title-gen) from main session kernel state #398/ACP_PASSTHROUGH one-click silent kill of all compression: no logs / no banners / panel invisible and non-clearable, all sibling env knobs take effect in silence | ACP_PASSTHROUGH 一键静默关死全部压缩:无日志/无横幅/面板不可见且不可清除,同族 env 旋钮全部生效于无声 #403 为原地 refold 留的口子);而内核 kernel/src/compress.ts 的 compress failure is self-amplifying: failed calls are hidden as orphan refs, making the same range permanently un-compressible #2362 死引用逻辑只检查每个区间的两个边界引用:已知(byRef)、不可见、且不被任何活跃块覆盖 → 记入 deadRefs。你的 m00774–m00860 这类区间只覆盖到 15 个块中的一部分,边界引用落在所有块之外 → 穿过守卫 → 被记 DEAD。完全吻合日志。
    2. 宿主无条件提交克隆状态放大伤害:即使零块创建,ctx.session.state = res.state 照常执行,墓碑落盘。
    3. 层次判断:你看到的「cannot be anchored … recorded as DEAD」是症状;根因是宿主侧豁免判据(span 重叠)与内核墓碑判据(逐边界引用覆盖)不一致,由「瞬态视图为空」这一只有宿主才知道的事实触发。内核把 input.messages 当作客户端权威视图是其契约,无法区分「宿主丢了视图」和「客户端真删了消息」——所以区分逻辑只能放在宿主侧,[bug] 大会话 compress 持续失败:ctx has 0 message(s) 时所有 ref 被判悬空(cannot-anchor),报错归因误导 #3126 放的位置是对的,只是判据留了缝。
    4. 方案评估:你提的方案一(视图为空且有映射 refs 时无条件拒绝)成立且充分。我额外验证了「放行是否可能服务合法操作」:空视图下内核端点一律锚定不上(refold 需要 restoredInline,而 inline 解折叠会把原文放回视图 → 视图非空,根本到不了这个分支);唯一理论上能成功的形状(内联恢复过的块在被逐出后立刻精确重折)即使被拒,下一轮也能原样成功。内核侧改判据(方案二)考虑过但不采纳:内核契约正确、改动需人工门禁,且宿主侧修复已闭环。

    正在修:守卫改为无条件拒绝 + 更新现有测试(现有一条测试恰好钉死了这个错误放行)+ 新增按你日志的多区间形态回归测试,完成后开 PR(Closes #3859)。

  2. ranxianglei commented on Oct 19, 2026

    @ranxianglei
    Owner

    🤖 Powered by ework · qwen3.8-27b

    (ai: vllm-qwen/qwen3.8-27b)

    修完了,PR: #3863 (Closes #3859)。先说结论:你提的两个方向里,第一个(守卫无条件化)方向对但过头了,最终实现是更精确的变体;第二个(kernel 区分「输入不可用」与「真死 ref」)不采纳,理由见下。

    Root cause (verified against your v0.1.201 tree). The #3126 gate exempted any batch whose ranges span-overlapped an active block span. The kernel's dead-ref pass (#2362) tombstones per BOUNDARY ref not point-covered by an active block — span overlap ≠ per-ref coverage. With a fully empty view nothing can anchor (visibleBlockAnchor/activeOwnerAnchor both return null), so the exemption only ever handed block-touching ranges to a kernel that failed them AND tombstoned their uncovered boundary refs; the host commits the cloned state even when zero blocks land (src/stream.ts:855), persisting the DEAD records. Your m00774–m00860 overlapped some of the 15 blocks while its boundary refs sat in the gaps between them — exactly the escape hatch.

    Why not fully unconditional. A blanket refusal regresses pinned #403/#2748 behavior: after a full-block inline restore, a later same-span re-compress must still land from state alone (tests/refold-wiring.test.ts pins three variants, including proxy-shaped empty views). My first reply here said "unconditional" — I built that version and these refold pins broke, which is how the final rule was found.

    The shipped rule. On an empty view with mapped refs, the gate yields to the kernel ONLY when every message-ref boundary of every range is point-covered by some active block's effectiveMessageIds (the state-alone refolds plus their "already compressed" / net-shrink diagnostics; pure bN..bM calls are never examined by the dead-ref pass and stay vouchable). Anything else → the whole batch is refused up front, zero state writes, same retry-after-next-model-request receipt. The predicate mirrors the kernel's danglingMessageRefs line-for-line so the two sides cannot drift apart again over which refs would get tombstoned.

    Kernel-side option rejected: the kernel treats input.messages as the authoritative client view by contract — it cannot know the host evicted its transient copy; the disambiguating signal exists only host-side, where #3126 already lives. No changes under kernel/.

    Behavior change (old → new): empty view + mapped refs + any range with a boundary ref covered by NO active block — old: passed to the kernel → "cannot be anchored" failure + those refs recorded DEAD and persisted; new: refused up front as gate:no-visible-context, nothing written, retry after the next model request restores the view. Everything else unchanged (non-empty views, ref-less sessions, proxy-mode loops, all covered-boundary shapes).

    Verification. New tests/issue3859-no-visible-context-overlap.test.ts (your multi-range shape, a mixed batch, the pure bN..bM row, non-empty-view negative row) + flipped #3126 pin. Both corruption shapes verified RED on the pre-fix code via stash-and-rerun; refold-wiring #403/#2748 pins green unchanged; typecheck + build clean locally; full-suite verdict on CI for this head.

    结构化八问

    1. 是什么问题:会话被逐出内存后(视图为空、ref 表仍在),模型发起的 compress 只要有一个 range 碰到活动块就绕过 no-visible-context 守卫,空消息列表进内核,没被块覆盖的边界 ref 被永久记成 DEAD。
    2. 什么原因导致的:守卫用「range 与块跨度重叠」判断放行,内核用「边界 ref 是否被某块逐点覆盖」决定埋死——两侧谓词不一致;且空视图下任何端点都无法锚定,放行只会失败+写墓志铭。
    3. 影响范围与严重程度:仅 plugin 车道(pi/omp/opencode/dsh native)会话在「被逐出/进程重启之后、下一个模型请求之前」窗口内发起碰块 compress 时触发;活 ref 被误标 DEAD(下一轮模型请求重发对话后内核会自动清除,但期间 acp_status 显示错误的 DEAD 状态、回执误导模型放弃重试这些范围)。能用但有明显问题 → p2。
    4. 怎么修复的:守卫的放行条件从「跨度重叠」改为「每个 range 的每个 m-ref 边界都被某个活动块逐点覆盖」(即 ACP_PASSTHROUGH one-click silent kill of all compression: no logs / no banners / panel invisible and non-clearable, all sibling env knobs take effect in silence | ACP_PASSTHROUGH 一键静默关死全部压缩:无日志/无横幅/面板不可见且不可清除,同族 env 旋钮全部生效于无声 #403/手动 compress 仍接受零/负净收益折叠,先改写缓存再提示上下文增长 #2748 纯状态 refold 及其诊断路径),其余整批前置拒绝、零状态写入;谓词与内核 danglingMessageRefs 逐行镜像防漂移。不做无条件拒绝——那会回归已钉死的 refold 行为,refold-wiring 的三个 pin 实测拦下了第一版方案。
    5. 是否有回归问题:非空视图、无 ref 新会话、proxy 模式回路(视图永不为空)、所有覆盖边界的 shape(refold/net-shrink/already-compressed/纯 bN..bM)保持原行为,均有测试钉住;唯一有意收紧:混合批(一半可服务+一半含未覆盖边界)从「半着陆+另一半写墓碑」变为「整批拒绝」,回执提示下一轮重试。高危模式逐项过:无语义漂移(拒绝文案与旧版一致)、无吞错、无阈值/时序/输出格式变化。
    6. 是否破坏缓存:不涉及——压缩块内容、prefix cache、增量游标、去重键均未触碰;被拒批次零状态写入。
    7. 是否改了设计方向:不涉及架构转向;只是把 [bug] 大会话 compress 持续失败:ctx has 0 message(s) 时所有 ref 被判悬空(cannot-anchor),报错归因误导 #3126 守卫的放行谓词修正为与内核死 ref 判定同一口径。
    8. 是否引入了新的配置:不涉及——无新 env/config/默认值。

    CI 绿后可合并,合并后本 issue 由 PR 自动关闭。

  3. manojVivek commented on Oct 20, 2026

    @manojVivek

    Additional related observation on billion-context 0.1.200 (3af88f2), Pi on macOS, Node v25.9.0. I am not asserting the same root cause as the 0.1.201 overlap/tombstoning path above.

    • The canonical local plugin status endpoint reported 0 tool / 0 text messages, but 41 active compressed blocks and about 13.9K summary tokens. Mapped refs remained present.
    • Earlier attempts reported unanchorable/dead ranges. Later attempts correctly refused with cause: no-visible-context and stated that nothing was marked dead; the three-failure circuit breaker eventually stopped calls.
    • New model requests, user messages, and fresh tool results occurred between attempts, but the next attempts still encountered an empty message view. I cannot confirm that retrying after the next turn recovers this case.
    • Meanwhile, automatic context nudges repeatedly listed the same source ranges and told the agent to compress them. This produced a nudge/refusal loop rather than useful context reclamation.
    • Status and actual billed context also differed substantially (about 13.9K local summary estimate versus 108.8K billed input). No successful fold was claimed, and no configuration, conversation state, or tombstone was manually edited.

    Could automatic nudge eligibility use the same live-view guard as compression, and avoid inviting retries until a view has actually been restored? A distinct supported path for refolding existing blocks when original messages are unavailable would also help. This report intentionally excludes session IDs, conversation contents, project paths, and credentials.

  4. manojVivek commented on Oct 20, 2026

    @manojVivek

    Follow-up on the same host, 0.1.200 (3af88f2): the suggested recovery for an already-compressed block can also be unreachable without changing the running tool implementation.

    • compress rejects a tier-2 block span with Refold blocked: blocked by b864 (not restored) and instructs decompress with inline:true.
    • The advertised decompress schema has no inline property. Forwarding args: {blockId: "b864", inline: true} through /__bili/plugin/tool still returns an automatic file pointer, with either nativeCaller:true or false. No restore-in-place flag is set.
    • Read-only inspection of the installed dist/index.js shows resolveDecompress unconditionally selects an automatic file when body.length > 1e4. That successful file-return branch occurs before markBlockRestoredInline. args.inline is not checked there.
    • The observed block body was 43,359 characters. Returning its file and reading it is useful for recall, but does not satisfy the separate refold guard. Repeatedly following the advertised inline recovery therefore leaves the same large block unrecoverable for tier-2 refolding.

    No session state, database, source, configuration, or running process was patched. Compression is being treated as unavailable rather than claiming a successful fold or bypassing the guard. A supported, bounded inline-restore contract (or an explicit readback acknowledgment) appears necessary, together with keeping host schemas/guidance synchronized with that contract.

  5. ranxianglei commented on Oct 21, 2026

    @ranxianglei
    Owner

    🤖 Powered by ework · qwen3.8-27b

    (ai: vllm-qwen/qwen3.8-27b)

    查重: 与 #3859 不同机制(#3859 = 空视图守卫绕过导致误标 dead;本条 = 大块 inline 恢复路径不可达),open/closed issue 检索亦无匹配条目(restoredInline / refold blocked / decompress toFile / inline 等签名)。已单独立项 #4080(带来源标记)。

    你列的三点观察全部经代码独立核实,v0.1.200(你报告的 3af88f2)与当前 master 均成立:

    1. 提示文案确实承诺 inline:true — kernel/src/compress.ts:520:Refold blocked: … Restore the affected block(s) inline (decompress with inline:true), then recompressing the same range updates them in place。
    2. host 端从未实现该参数 — decompress 四线 schema(src/compress-tool.ts 的 BILI_DECOMPRESS_TOOL*)由 kernel 基座 schema + 可选 startId/endId(CCR)/imageRef/full/toFile 组成,无 inline 属性;src/decompress-shared.ts 全文不读取 args.inline,照传无效。
    3. 大块永远拿不到标记 — src/decompress-shared.ts:246:body.length > 10000 无条件写临时文件并在 line 253 返回文件指针,该分支位于 markBlockRestoredInline(line 273)之前;注释明确 toFile 路径 "stays byte-identical to pre-refold behavior (no flag, no hint)",tests/refold-wiring.test.ts 有对应 pin("toFile spill (>10K chars) does NOT flip the flag — frozen contract")。range restore 路径同款阈值在 line 444。

    Triage 结论:真实缺陷,属「两侧契约错位」——内核文案承诺了一个 host 端不存在的参数,>10K 字符块唯一可执行的恢复步骤结构性不可达,模型每轮空转一次「compress→被拒→decompress→拿文件→再 compress」。toFile 不落 flag 本身是有意钉死的契约(材料在文件而不在对话里,代理无法确认模型真读了),不宜单方面推翻;要修的是错位本身。三种方向是设计决策,需你拍板(完整复现/定位/验收建议见 #4080):

    八问

    1. 是什么问题: 完整正文 >10K 字符(约 >2.5K token)的块,同 span 再折叠/tier 蒸馏被 Refold blocked 拒绝后,内核给出的唯一恢复指令(decompress with inline:true)在协议上不存在,模型反复照做永远拿到文件指针,该块摘要永远无法原地更新。
    2. 什么原因导致的: kernel 与 host 契约错位——内核文案承诺 inline:true,host 端 decompress 从未实现(schema 未公布、执行不读取);同时 >10K 自动落盘分支在 markBlockRestoredInline 之前返回,且该分支被有意钉为 frozen contract。
    3. 影响范围与严重程度(按选项): 共同触发条件 = 块正文 >10K 字符 且 模型需对其同 span refold/蒸馏——这正是上下文增长主路径(大 tier-1 块蒸 tier-2)。A(仅改文案): 消除误导但死锁仍在,体验级缺陷(p3);B(加有界 inline 参数): 解开死锁、默认行为不变(p2→解决);C(toFile 也置 flag): 同样解开但引入「未读文件即 refold」语义风险。现状总体 p2——特定条件下核心再折叠不可用,无数据丢失、无缓存泄露。
    4. 怎么修复的(推荐 B): decompress 增加 opt-in 有界 inline:true 覆盖参数——schema 四线公布 + 执行端尊重:显式传入时跳过 10K 自动落盘,整块内容进 tool result 并置 restoredInline(可设字符上限防失控)。它实现的正是内核已在广告的行为,提示文案不用动,默认路径字节不变。若 B 被否,退回 A 作最小止血(kernel 两行文案,仍需 kernel-change 声明与版本纪律)。
    5. 回归风险(按选项): A: 零行为变更(纯文案,但属 kernel 变更,走版本纪律);B: 默认路径字节不变(opt-in),风险集中在四线 schema 同步与 golden 更新;C: 推翻已钉契约、refold-wiring pin 要翻,另有摘要质量静默下降风险。
    6. 是否破坏缓存: B 的 inline 模式让大内容进 tool result → 入客户端历史 → 该会话一次性 prefix cache 成本(受上限约束);A/C 不改投递形态,无缓存影响。
    7. 是否改了设计方向: 不改——B 是让 host 对齐内核已在广告的契约(host 跟随 kernel),不动 restoredInline 标记语义本身。
    8. 是否引入新配置: B 新增一个 tool 参数(非 env/config 文件/CLI flag),属 wire surface,需四线 schema + golden wire-contract 同步;若设上限,默认值须同 PR 定死。

    选项对比

    选项 做法 优点 代价/风险
    A. 仅改文案 内核 hint 按 host 实际能力措辞,不再承诺 inline:true 零行为变更 治标:恢复路径仍不可达,死锁仍在,只是不再误导
    B. 加有界 inline:true(推荐) schema 公布 + 执行尊重:跳过 10K 落盘,内容进 tool result 并置 restoredInline(可设上限) 内核既有提示变真;默认路径字节不变;直接解死锁 wire surface 变更(四线 schema + golden);tool result 体积放大 → 历史/cache 一次性成本,上限需定值
    C. toFile 也置 flag 文件指针同样视为「模型持有材料」 无需新参数,提示立即变真 推翻已钉 frozen contract(pin 要翻);未读文件凭旧记忆 refold 的风险

    标签: bug / pi / scope/kernel / p2(代码核实的缺陷;Pi 会话观测;失败域为压缩/上下文管理核心逻辑;特定条件下能用但明显坏,无数据丢失)。

    等待你对 B/A 拍板后实施(#4080 内跟踪)。#3859 主线修复 PR #3863 与本条无关,不受影响。

  6. ranxianglei commented on Oct 21, 2026

    @ranxianglei
    Owner

    🤖 Powered by ework · qwen3.8-27b

    (ai: vllm-qwen/qwen3.8-27b)

    补充(PR #3863):首轮 CI 暴露两个 master 上已红的门禁(与本修复无关;查重 open/closed issue 与 PR 均无对应修复条目):docs-sync(已提交的配置文档含两行被取代的重复行)与 unused-exports(maskUrlForPublish 无任何 import)。按几行级 CI 红处置纪律在本 PR 内以独立标注 commit 修掉(22477f56d 重新生成删 4 行陈旧文档;f9c86090d 去掉一个 export 关键字转模块私有),零行为变更,详情已写入 PR 描述。等重跑结果。

  7. ranxianglei commented on Oct 21, 2026

    @ranxianglei
    Owner

    🤖 Powered by ework · qwen3.8-27b

    (ai: vllm-qwen/qwen3.8-27b)

    状态更新(PR #3863):评审期间 master 前进了约 30 个合并,#3863 变成 dirty。已 rebase 到当前 master(39fb841d3)并强推——零冲突、零语义变化(该窗口内没有上游提交触碰 src/stream.ts;两个顺手修复既有红的标注 commit 在新 base 上仍然成立并保留)。rebase 后本地验证全绿:typecheck 干净、定向测试 17/17(#3126 + #3859 + refold-wiring)、docs-sync 与 unused-exports 门禁干净。CI 正在新 head(26465c656)上跑,绿了补结果。

  8. ranxianglei commented on Oct 21, 2026

    @ranxianglei
    Owner

    🤖 Powered by ework · qwen3.8-27b

    (ai: vllm-qwen/qwen3.8-27b)

    修复完成,PR #3863 已就绪(head 573ae424e,rebase 到当前 master 56a0f5a46)。结构化结论如下:

    1. 是什么问题
    会话消息视图为空(客户端断连/被逐出)但 refs 仍映射时,compress 请求只要「碰到」某个活跃块 span 就能绕过 #3126 守卫进入内核;空视图下内核无法锚定任何 ref,其 dead-ref 测试把未被块点覆盖的边界 ref 记为 DEAD 并持久化——活着的 ref 被永久标死,receipt 还禁止模型重试这些范围。

    2. 什么原因导致的
    两层谓词不匹配:宿主守卫的豁免条件是 span 重叠(任一请求范围与某块 span 相交即放行);内核的墓碑条件是 按边界 ref 的点覆盖(danglingMessageRefs 只查 startRef/endRef 是否在某块的 effectiveMessageIds 里)。范围可以盖住一个块、而两端点都落在块间空隙(正是你撞到的 m00699–m00721 / m00774–m00860 / m00869–m00870 形状)→ 过守卫 → 被内核标死;且宿主对零块结果也会提交内核克隆的状态,墓碑直接落盘。更深层:内核按契约把 input.messages 视为权威客户端视图,分不清「宿主瞬态视图被逐出」和「客户端真删了消息」——这个信号只在宿主侧存在,守卫放宿主侧是对的(#3126 位置正确,豁免条件错了)。

    3. 影响范围与严重程度
    p2:能用,需特定序列(会话被逐出/重启后、下一次模型请求恢复视图前,模型恰好发起触碰块的 compress)。期间 acp_status 显示 DEAD、receipt 误导模型放弃那些范围;视图恢复后墓碑会自愈(tombstone-with-clear),无永久数据丢失、无缓存泄露。

    4. 怎么修复的
    守卫豁免条件从「span 重叠」改为精确镜像内核自身 dead-ref 测试的担保判定:视图为空且 refs 映射时,仅当所有请求范围的 message-ref 边界都被某个活跃块 effectiveMessageIds 点覆盖才放行(block-ref 边界不带 raw id,内核本就跳过,天然安全);任一边界未覆盖 → 整批拒绝 gate:no-visible-context,零状态写入。这样关死的恰好是墓碑面,合法路径一条不破。为什么不是首轮回复里说的「无条件拒绝」:我建了无条件版,tests/refold-wiring.test.ts 里 4 个钉住既有行为的测试变红——#403/#2748 的 state-alone refold(decompress 置 restoredInline 后同 span 重压、growing-refold net-shrink 门等)依赖空视图下进内核落地,其边界 ref 恰是块点覆盖的。担保版保住这四个钉,同时关掉 #3859 形状。

    5. 是否有回归问题
    逐项过过:四个曾被无条件版打红的 refold-wiring 钉全部绿;新增负向行——非空视图永不命中门、混合批(一半可担保一半不可)整批拒且不动可担保半区的 summary;proxy 模式不受影响(relay 恒传非空 originalMessages,守卫首检短路);#403/#2748 的 refold/net-shrink/already-compressed 路径行为不变;fail-fast 语义未变(仍是显式 refused receipt);无阈值/时序/输出格式漂移。

    6. 是否破坏缓存
    不涉及——只在内核执行前加了一个拒绝分支,不触碰任何渲染/前缀缓存路径。

    7. 是否改了设计方向
    没有——仍是 #3126 架构「宿主按视图可用性决策、内核按输入执行」,只修正了宿主的决策谓词,让它与内核的实际墓碑测试对齐。issue 里提的内核侧选项(区分不可用输入与真死 ref)未采纳:内核在其输入契约下行为正确,且内核改动需 owner 前置审批,宿主侧修复已足够。

    8. 是否引入了新的配置
    没有(无新 env/config/CLI)。

    行为变更披露(旧→新):空视图 + 含未覆盖边界 ref 的范围请求(含「span 重叠但端点在块间空隙」、多范围跨空隙、越过最后一块等形状):旧 = 过守卫 → 内核报 cannot-anchor 错 + 未覆盖边界 ref 记 DEAD 落盘;新 = 整批先行拒绝 gate:no-visible-context,零状态写入,下次模型请求恢复视图后可重试。其余形状(非空视图、无 refs 新会话、纯 bN..bM 蒸馏、全边界点覆盖的 refold)行为不变。

    CI 现状(如实报告):全部静态门禁绿(unused-exports、docs-sync、kernel-guard、lane-guard、version-guard、lock-sync、config-surface-guard、test-import-graph、build-artifact、mysql-contract、plan 等);定向回归本地绿(issue3126 + issue3859 + refold-wiring = 17/17)。5 个测试分片红,全部是 master 既有红,与本 PR 无关:(1) wire-anthropic round-trip ×2 = tests/verif-wire-anthropic-roundtrip.test.ts:28 硬编码开发机绝对路径 /home/dog/tmp/bili-verif2/...,任何非作者机器 ENOENT;(2) view-latch ×1 = 无求解器环境不 skip 直接抛错(违反 KDD #14「无求解器自动 skip」契约);(3) windows 分片另有 #3769 已知 flake。证据:master 自己的 head(56a0f5a46)运行在同样 5 个分片上以相同测试名失败。这些问题已有立项(#4083/#4088/#4081)与在途修复 PR(#4084/#4089),按查重规则未另开 issue、未把修复混入本 PR。

    收工判定:代码与门禁层面可合;测试泳道全绿需待 #4084/#4089 合入后在本 head 重跑(或 owner 例外放行)。PR:#3863

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions