Repository navigation
Description
啥问题
scope/storage 标签下五个悬而未决的缺陷/缺口,本 issue 收口为一个 PR(存储收尾第三波),全部按机制修,不打补丁:
- Unified Storage: MERGE_STATE_FIELDS and mergeState out of sync — full mode hydration drops sweptOrphanCallIds/pendingOrp | 统一存储 MERGE_STATE_FIELDS 与 mergeState 不同步:full 模式水合丢 sweptOrphanCallIds/pendingOrphanSweepMinRef(#2388 类) #4192 —
MERGE_STATE_FIELDS与mergeState字段清单不同步,full 模式水合丢sweptOrphanCallIds/pendingOrphanSweepMinRef(The CompressionState cloner with an explicit field list silently drops newly added optional fields (deadRefs proven harmful; kernel hydrator still missing three items) | 显式字段列表的 CompressionState 克隆器静默丢弃新增可选字段(deadRefs 已实证有害;kernel 水合器仍缺三项) #2388 显式字段克隆病第 N 次复发)。 - [Bug][storage] Retained orphan originals freeze unified session ingestion; cold loads restore hours-old state #4216 — 孤儿形状
record()早退冻结一切后续入库:会话行/块/尾巴永久停滞,冷加载回小时级旧态(真实事故:requests=876 的会话停在 390,502 循环)。 - storage full mode: loadSync serves the sqlite index and bypasses the envelope id-mismatch guard (#2671-era tests fail locally) #4004 — full 模式 unified-first 加载天然绕过信封 id 守卫(Storage layer: four concrete defects (filename coupling, multi-writer heuristic, non-atomic content-store write, immortal compressed sessions) #2671 时代四测试非确定性红)。
- [storage] #3762 media CAS split only halves the payload: the raw wire sidecar still embeds a second full copy — on real data the split yields 1.89× instead of 33.8× #4069 — 媒体 CAS 只拆了一半:
rawOpenaiContent等 wire sidecar 仍内嵌整份 base64,实测 1.89×(应 33.8×);kernel 每张新图仍产两份。 - [storage] 0.1.202 existing install: 12.6 GiB of refcount=0 orphans (87% of content bytes), still growing on a long session — interim reclaim before the GC/CLI release? #3987 —
releaseContent减到 0 不删行,孤儿只增不减(真实库 92,004 行零引用 11.32GB),gc 手动入口形同虚设。
机制级修法
1. 状态字段单一事实源(修 #4192,连带 #2388 病根)
kernel 导出 COMPRESSION_STATE_FIELDS(kernel/src/state-merge.ts 的清单为权威);host 侧 persist.ts mergeState、ingest.ts MERGE_STATE_FIELDS、buildSession、convergeToDisk 全部改为消费同一常量;parity 测试钉 kernel 清单 === host 清单 === 水合往返全集。从此加字段只改一处,漏项结构性不可能。
2. 孤儿形状可表示化(修 #4216,废除冻结)
孤儿内容随每次 full/delta ingest 计划在同一事务写入 sessions.orphans_json(合并语义);record()/enqueueAsyncRecord()/importRecord() 删除孤儿早退——正常入库恢复,块/行/尾巴照常前进,supersede owner 21b70f4 钉死的"imports=0"语义(该语义在视图不可表示时正确;loadLegacyView 已会水合 orphans_json,形状已可表示,前提消失)。reapBlocks 台账保留(幂等,处理过渡期旧行)。MySQL twin 同步。回归钉:issue 的确定性 repro(requests=876 不再停在 390)+ 冷加载新鲜度 + 双后端契约。
3. full 模式权威语义定案(修 #4004)
定案:full 模式下索引就是加载路径,信封是恢复源——信封被篡改而索引完好时,服务索引是正确行为,不是守卫被绕过。四个 #2671 时代测试钉到 legacy/shadow 模式(信封守卫在那条路上仍然活着);新增 full 模式测试钉"索引权威服务"语义。守卫本身保留(legacy/shadow 路径),不加冗余的双载体校验。
4. 媒体 CAS 全量拆分(修 #4069,14.2×)
splitMediaContent() 走遍全部 raw sidecar 槽位(rawOpenaiContent/rawOpenaiContentParts/rawAnthropicBlock/rawGoogleParts/rawResponsesItem);引用放兄弟键(rawOpenaiMedia 等),url 槽位保持字符串形状(所有 startsWith("data:") 读点零改动);hydrateMedia 学会回填全部槽位;MIN_SPLIT_BYTES(2048)防小图倒贴;mysql.ts twin 同步;字节恒等往返钉现有金桩;存量行 lazy(不回填,新写入即紧凑)。
5. 引用计数归零即删(修 #3987)
releaseContent 归零时不再留行:同事务 DELETE ... WHERE refcount<=0(或零命中即删的单语句);gc/deleteSession 的清扫保留为兜底。孤儿积累从"需要人跑 gc"变成"结构上不存在"。
验收
- Unified Storage: MERGE_STATE_FIELDS and mergeState out of sync — full mode hydration drops sweptOrphanCallIds/pendingOrp | 统一存储 MERGE_STATE_FIELDS 与 mergeState 不同步:full 模式水合丢 sweptOrphanCallIds/pendingOrphanSweepMinRef(#2388 类) #4192:水合往返字段全集相等 + digest 收敛(driftedSessions 清空)。
- [Bug][storage] Retained orphan originals freeze unified session ingestion; cold loads restore hours-old state #4216:issue 确定性 repro 双 facade(sync+queued)转绿;重启冷加载 = 最新 requests。
- storage full mode: loadSync serves the sqlite index and bypasses the envelope id-mismatch guard (#2671-era tests fail locally) #4004:四测试模式钉定后确定性;full 权威语义有钉。
- [storage] #3762 media CAS split only halves the payload: the raw wire sidecar still embeds a second full copy — on real data the split yields 1.89× instead of 33.8× #4069:双拷贝行拆后消息行字节 < 1KB;往返逐字节恒等;双后端契约。
- [storage] 0.1.202 existing install: 12.6 GiB of refcount=0 orphans (87% of content bytes), still growing on a long session — interim reclaim before the GC/CLI release? #3987:释放归零后行消失;真实库 gc 后 VACUUM 收缩。
- 全家族回归 + 双门 + ci:full。
明确不在本 PR
- 判官-渲染车道豁免(Egress Judge false positive on Snapshot Render Lane I2: prefix comparison baseline needs to be aware of the render view | 出站判官对快照渲染车道误报 I2:前缀比对基准需要感知渲染视图 #4241,独立)。
- CI intermittently red: Two storage hydration tests fail to read session records just flushed to disk under parallel load | CI 偶发红:两个存储水合测试在并行负载下读不到刚落盘的会话记录(#3769 家族) #4041 CI 偶发(观察)。
- [Enhancement] No warning when dump capture is enabled without a retention limit: 51.9 GB silently written over 10 days ( | [Enhancement] 开启 dump 捕获但未设保留上限时无任何提示:10 天静默写入 51.9 GB(#2412 后续) #3851 dump 警告(小增强,后续)。
Activity
[-]存储收尾第三波:状态字段单一事实源 + 孤儿可表示化 + full 权威定案 + 媒体 CAS 全量拆分 + 归零即删(#4192/#4216/#4004/#4069/#3987)[/-][+]Storage wrap-up wave 3: Single source of truth for status fields + Orphan representability + Authoritative "full" resolu | 存储收尾第三波:状态字段单一事实源 + 孤儿可表示化 + full 权威定案 + 媒体 CAS 全量拆分 + 归零即删(#4192/#4216/#4004/#4069/#3987)[/+]ranxianglei commentedon Oct 22, 2026
(ai: vllm-qwen/qwen3.8-27b)
查重: 不重复。 本 issue 是五个已知 open 缺陷的收口单,不是新发现;且其中三项已有在途 PR(#4218/#4075/#4011),本 PR 将它们cherry-pick 收编进同一个分支(保留原提交与作者署名,不另开重复 PR)。查重声明:与 #4216/#4004/#4069/#3987/#4192 相关但不同——它们是分项缺陷记录,本单是收口执行单。
标签: bug / dsh / codex / scope/storage / p1(缺陷收口;证据车道 dsh+#4216 codex;存储域;含 p1 级项 #4216 冷加载旧态、#3987 存储只增不减)
对 master(c87185a,v0.1.204)逐项核实:
src/storage/ingest.ts:35-54MERGE_STATE_FIELDS 仍缺sweptOrphanCallIds/pendingOrphanSweepMinRef,persist.ts mergeState已带 → 不同步仍在COMPRESSION_STATE_FIELDS单一事实源 + host 四处消费 + parity 钉shadow.ts四处孤儿早退仍在(:280/:487/:780/:873),finishPlan无 orphansJsonsplitMediaContent仍只拆顶层 imageBase64;wire sidecar 双拷贝仍在releaseContent归零不留行问题仍在(gc 兜底已有,#3911)执行计划(分支
2026-10-22_storage-wrapup-wave3,已从 origin/master 切出):kernel/src/state-merge.ts导出常量清单,PR 正文带kernel-change:声明——设计由本 issue 直接指定,视为 owner 批准;host 侧 persist.ts/ingest.ts/buildSession/convergeToDisk 全部消费同一常量,parity 测试钉 kernel 清单 === host 清单 === 水合往返全集);releaseContent归零即删,gc/deleteSession 清扫保留为兜底);行为变更预告(最终回复会按八问完整披露):孤儿形状从「跳过镜像」变为「正常入库+台账」;媒体拆分阈值统一 64KiB(v0.1.203 未发版,无存量影响);full 模式索引权威语义被测试钉死(非代码变更);journal 语义从增量合并转为权威替换。
ranxianglei commentedon Oct 22, 2026
Wave 3 delivered as #4249 (one PR, five mechanisms, per this issue's directive):
COMPRESSION_STATE_FIELDS = Object.keys(mergeState(createInitialState())), newsrc/state-hydration.ts); a parity test fails the momentcreateInitialState()grows an unknown key. The hand-copied list is gone.releaseContentdeletes on zero on both backends (UPDATE … RETURNING+ delete); FK discipline made explicit (referencing rows deleted before release, five sites reordered). Orphans cannot structurally accumulate.sessions.orphans_json(merged∪, LazyBlockContents-aware). Mirror skips ×3 and the Storage wrap-up: 6 fixes + switch default mode to full (single PR) | 存储收尾六修 + 缺省模式翻 full(一个 PR 统一处理) #3818 writerOff JSON valve deleted; the freeze repro (post-reap saves stuck atrequests=390) is pinned and fixed.data:<mime>;base64,<payload>≥2 KiB swapped for{$media:<hash>}, byte-identical hydration, one CAS row per payload across both channels (issue corpus 1.89× → 33.8×). Small payloads stay inline.legacy; a new test pins the authority semantics (reject only when both carriers are gone/tampered).All storage families green locally (unified/reap/media/archive/bounded/scale/engine-proofs/contract/replicas/writer-off/durability/pointers/proxy-persist/issue2129); goldens regenerated once for a master-inherited stats shape (#4198), zero wire delta from this PR.
ranxianglei commentedon Oct 22, 2026
(ai: vllm-qwen/qwen3.8-27b)
收到,#4249 已按 PR Review 规范完整过审(独立读全部 diff + CI 日志,未照抄本 issue 结论)。先交代一句透明事项:我此前在另一会话里对同一 issue 起了平行实现(仅本地分支,从未 push),确认 #4249 是正式交付后已放弃该分支,不产生第二个 PR。
审核发现:两个回归(均已当场修掉,push 到 PR 分支)+ 一个覆盖面缺口(待你拍板)
🔴 回归 1(已修,commit
4b23f110c):chat golden 重新生成是错误的。CI 四个失败分片(ubuntu22×2、windows22×2)全部失败在同一点——运行时算出的 digest 是 master 旧值(187920f4…),而不是 PR 提交的新值(2b39faa2…)。即「drift 在干净 base 上可复现」的说法在 CI 的 base+head 上不成立:这个 PR 对 chat wire 零改动,CI 证明旧 golden 才是对的。本地重新生成大概率受了环境污染(本地 bili 实例/残留配置)。已把tests/golden/storage-render/chat.json恢复为 master 版本。🔴 回归 2(已修,commit
e0a92d71c):src/state-hydration.ts用包名import … from "acp-kernel"(解析到kernel/dist),但它是存储缝加载图的新成员(backend.ts → ingest.ts → state-hydration.ts),而 CI 的 mysql-contract job 走npm install --ignore-scripts、从不构建 kernel →ERR_MODULE_NOT_FOUND,mysql 车道整个挂掉。存储层既有约定就是相对引用 kernel 源码(ingest.ts引../../kernel/src/refs.js),已改为相对导入,tsup 打包行为不变。🟡 缺口(需拍板):#4069 的正则只覆盖
data:<mime>;base64,…形态(≥2048 字符),裸 base64 载体没拆——anthropicrawAnthropicBlock.source.data、googlerawGoogleParts[].inlineData.data仍是整份内嵌,这两条车道的消息行不会降到 <1KB(issue 验收口径)。纯成本问题、无正确性影响(OpenAI/Responses 车道全覆盖,往返字节恒等有钉)。两条路:①按载体字段定向遍历补上(#4075 原方案的做法,需要新增 fixture+负例测试,防止误伤任意长字母数字串);②明确接受「data-URL 形态全覆盖、裸 base64 留后续」并写进 PR 描述。我推荐 ①,可以直接在这个分支补。另有两处小修一并 push:⑨ 测试重写时丢了原有的精确文本断言(只查了键存在),已恢复字节级断言(
1fcc87f8b);其余审核点见下。结构化八问
sessions.orphans_json(∪ 合并,LazyBlockContents 感知)、full 模式钉死「索引持记录即服务,双载体皆坏才拒绝」、正则换 data-URL 载荷为 CAS 标记(字符串形状保留,读点零改动)、归零即删(UPDATE 守卫 + 条件 DELETE,FK 纪律五处重排为先删引用行再释放)。合并判定:两个回归修复已 push(
311c7968e..1fcc87f8b),等这轮 CI 转绿即可合;缺口 C(裸 base64 载体)你拍板后我在同分支补或另开 follow-up。合并后建议把 #4011/#4075/#4218 三个被取代的 PR 按 duplicate 流程关闭(带说明评论)。ranxianglei commentedon Oct 22, 2026
(ai: vllm-qwen/qwen3.8-27b) 上轮 review 的 mysql-contract 红灯第二跳已修并推上 PR 分支(
175feab00):session.js导入scrubTurnSeparatorIds,而session.ts顶部 value-imports acp-kernel——seam 图仍深一层够到未构建包(CI 报错从 "imported from state-hydration.js" 移到 "imported from src/session.ts",同一错误类)。src/turn-separator.ts;session.ts保留 re-export(消费方 server/stream/fold-reconcile/tests 零改动);state-hydration 改从叶子导入。文档注释随代码原样迁移。kernel/dist后跑两个 contract 套件 = 11/11 绿(精确模拟该 lane 的--ignore-scripts环境);issue2627/state-fields/full-flip/orphan-representable 24/24;tsc 与 unused-export 门均绿。等这一跑的 mysql-contract 结果;绿了则全部 lane 齐绿。
ranxianglei commentedon Oct 22, 2026
(ai: vllm-qwen/qwen3.8-27b)
收口状态:windows 分片红灯已定性为环境性时序 flake(非本 PR 回归),加固小修已在分支上,新一轮全矩阵正在跑。
windows 分片二红的定性(按「同签名再红即升级」的预案走的)
失败点固定:
tests/wire-exit-matrix.test.tsabort 行的 SSE cell,断言upstream socket(s) destroyed after client abort (closed=0)。证据链:setTimeout(300)等上游 socket 销毁后直接断言;同一文件的 json cell 早就改成了轮询写法(30×100ms,注释明说「slow runners destroy the socket later」)——四个 cell 里只漏转了这三个。慢 runner 上 300ms 不够就红,与代码无关。src/server.ts/loop),本 PR 的 diff 全部在存储层(persist/state-hydration/storage/*)+ 一个零行为 re-export(session.ts),不触碰任何相关路径;测试文件本身也不在 PR diff 里。fix/compress-input-contract、handoff-latest-reply、wire-openai-preserve-part-extras、pair-fixpoint等多个无关分支同期各红在 windows 分片/存储 golden 测试上。master 的 windows 分片在其自己那跑里,这个 abort 测试是过的(ok 3166)。分支上的加固小修(d9963eb,非本会话提交)
我审核期间分支上多了一个提交(另一并行会话推的,我的本地克隆当时停在
175feab00,特此说明不冒领):把三个 SSE cell 的固定 sleep 换成 json cell 既有的 30×100ms 轮询惯用法。我已独立核验:纯测试改动(+15/−3,单文件)、不改任何 wire/源码语义、正是该 flake 需要的修法。旧 head 的 rerun 结果现在只作旁证,新矩阵才是闸。当前 CI 与合并判定
新 head
d9963eb5b全矩阵运行中:fast lane + mysql-contract + static-gates×2 已绿,6 个 test 分片在途(~13 分钟)。这一矩阵绿了即可合——此前发现的两处回归(golden 错误重生成4b23f110c、mysql 车道 import 断裂两跳e0a92d71c+175feab00)与断言牙齿恢复(1fcc87f8b)均已 push 且在新 head 上被覆盖验证。结构化八问(最终态)
遗留两件事等你:①缺口 C(裸 base64 载体 anthropic
source.data/ googleinlineData.data未拆,纯成本、无正确性影响)——选 ① 我在同分支补,或 ② 写进 PR 描述接受留后续;②合入后我把 #4011/#4075/#4218 三个被取代 PR 按 duplicate 流程关闭(先评论说明再关)。ranxianglei commentedon Oct 22, 2026
(ai: vllm-qwen/qwen3.8-27b)
✅ 新 head
d9963eb5b全矩阵绿:6 个 test 分片全过(含此前两次红的windows-latest, 22, 2——轮询加固生效)、mysql-contract、static-gates×2、全部 guard/快车道零坏点。收工判定:PR #4249 可合。 审核发现的两处回归(golden 错误重生成、mysql 车道 import 断裂)与断言牙齿恢复均已修并随矩阵验证;windows 红灯定性为测试自身时序竞态(固定 300ms sleep vs json cell 既有轮询惯用法)+ 同窗环境红风暴佐证,非本 PR diff 回归。八问全文见上一条回复。
等你两件事:①缺口 C(裸 base64 载体)拍板——同分支补或留后续;②你合入后我按 duplicate 流程关闭 #4011/#4075/#4218(先评论说明再关)。
Merge pull request #4249 from ranxianglei/2026-10-22_storage-wrapup-3
storage correctness wave 3: derived hydration fields, delete-on-zero …
ranxianglei commentedon Oct 24, 2026
存储收尾第三波全部随 PR #4395(row-identity-historian)合并交付:
后续 master 红余波(CI fully red (on master itself): #3250 WAL cap assertion failing across all platform shards — suspected that this mornin | CI 全红(master 自身):#3250 WAL cap 断言在所有平台分片失败,疑似今晨合入的存储层改动破坏 autocheckpoint 截断行为 #4506 WAL/media CAS)由 [PR] fix(#4506): release media refs on historian retire + budget the #3250 custom-cap slack (#4390 follow-ups) #4511 修复。收尾。