Skip to content

Storage wrap-up wave 3: Single source of truth for status fields + Orphan representability + Authoritative "full" resolu | 存储收尾第三波:状态字段单一事实源 + 孤儿可表示化 + full 权威定案 + 媒体 CAS 全量拆分 + 归零即删(#4192/#4216/#4004/#4069/#3987) #4242

Closed

Description

@ranxianglei

啥问题

scope/storage 标签下五个悬而未决的缺陷/缺口,本 issue 收口为一个 PR(存储收尾第三波),全部按机制修,不打补丁:

  1. 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 次复发)。
  2. [Bug][storage] Retained orphan originals freeze unified session ingestion; cold loads restore hours-old state #4216 — 孤儿形状 record() 早退冻结一切后续入库:会话行/块/尾巴永久停滞,冷加载回小时级旧态(真实事故:requests=876 的会话停在 390,502 循环)。
  3. 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 时代四测试非确定性红)。
  4. [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 每张新图仍产两份。
  5. [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"变成"结构上不存在"。

验收

明确不在本 PR

Activity

  1. changed the title [-]存储收尾第三波:状态字段单一事实源 + 孤儿可表示化 + 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)[/+] on Oct 22, 2026
  2. ranxianglei commented on Oct 22, 2026

    @ranxianglei
    OwnerAuthor

    🤖 Powered by ework · qwen3.8-27b

    (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)逐项核实:

    项 master 现状 处置
    #4192 src/storage/ingest.ts:35-54 MERGE_STATE_FIELDS 仍缺 sweptOrphanCallIds/pendingOrphanSweepMinRef,persist.ts mergeState 已带 → 不同步仍在 新实现:kernel 导出 COMPRESSION_STATE_FIELDS 单一事实源 + host 四处消费 + parity 钉
    #4216 shadow.ts 四处孤儿早退仍在(:280/:487/:780/:873),finishPlan 无 orphansJson 收编 #4218(外部贡献 @0XCocoa,CI 44/44 绿)
    #4004 信封守卫仍在 legacy/shadow 路径;full 权威语义未钉 收编 #4011(测试钉定,CI 绿)
    #4069 splitMediaContent 仍只拆顶层 imageBase64;wire sidecar 双拷贝仍在 收编 #4075(四 codec 全覆盖,CI 绿,独立复验 19.68×/无损)
    #3987 releaseContent 归零不留行问题仍在(gc 兜底已有,#3911) 新实现:归零同事务删行 + mysql twin

    执行计划(分支 2026-10-22_storage-wrapup-wave3,已从 origin/master 切出):

    1. cherry-pick [PR] fix: media CAS split covers all raw wire copies (#4069) #4075 → [PR] fix: keep unified session ingestion current with retained originals #4218 → [PR] fix: make full-mode storage tests deterministic (#4004) #4011 三个在途 PR 的全部提交(顺序按依赖,冲突就地按意图并);
    2. 实现 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(kernel 侧新增 kernel/src/state-merge.ts 导出常量清单,PR 正文带 kernel-change: 声明——设计由本 issue 直接指定,视为 owner 批准;host 侧 persist.ts/ingest.ts/buildSession/convergeToDisk 全部消费同一常量,parity 测试钉 kernel 清单 === host 清单 === 水合往返全集);
    3. 实现 [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 归零即删,gc/deleteSession 清扫保留为兜底);
    4. 定向测试过 → push → 开 PR(Closes 五缺陷 + 本单,Supersedes 三个在途 PR)→ CI 全局把关。

    行为变更预告(最终回复会按八问完整披露):孤儿形状从「跳过镜像」变为「正常入库+台账」;媒体拆分阈值统一 64KiB(v0.1.203 未发版,无存量影响);full 模式索引权威语义被测试钉死(非代码变更);journal 语义从增量合并转为权威替换。

  3. ranxianglei commented on Oct 22, 2026

    @ranxianglei
    OwnerAuthor

    Wave 3 delivered as #4249 (one PR, five mechanisms, per this issue's directive):

    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.

  4. ranxianglei commented on Oct 22, 2026

    @ranxianglei
    OwnerAuthor

    🤖 Powered by ework · qwen3.8-27b

    (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 载体没拆——anthropic rawAnthropicBlock.source.data、google rawGoogleParts[].inlineData.data 仍是整份内嵌,这两条车道的消息行不会降到 <1KB(issue 验收口径)。纯成本问题、无正确性影响(OpenAI/Responses 车道全覆盖,往返字节恒等有钉)。两条路:①按载体字段定向遍历补上(#4075 原方案的做法,需要新增 fixture+负例测试,防止误伤任意长字母数字串);②明确接受「data-URL 形态全覆盖、裸 base64 留后续」并写进 PR 描述。我推荐 ①,可以直接在这个分支补。

    另有两处小修一并 push:⑨ 测试重写时丢了原有的精确文本断言(只查了键存在),已恢复字节级断言(1fcc87f8b);其余审核点见下。


    结构化八问

    1. 是什么问题:五个存储缺陷收口——水合字段清单漂移丢字段、孤儿形状冻结入库、full 模式权威语义未定、媒体双拷贝、CAS 零引用行只增不减。
    2. 什么原因导致的:①字段清单手抄两份且已漂移(还差一层:两份清单的顺序都不一致,带值会话的两个 digest 侧天然永久漂移);②reap 形状在索引里不可表示 → record/import 早退冻结;③索引与信封谁是加载路径没有定案;④splitMediaContent 只拆顶层 imageBase64 槽位;⑤releaseContent 只减不删,清扫靠人跑 gc。
    3. 影响范围与严重程度:p1 级两处——[Bug][storage] Retained orphan originals freeze unified session ingestion; cold loads restore hours-old state #4216 冷加载回小时级旧态(真实事故 requests=876 停 390)、[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 真实库 92k 行/11.32GB 零引用累积;其余为成本(媒体 ~17× 差距)与潜在正确性(字段丢失→缓存断裂)。
    4. 怎么修复的:单一事实源(字段清单从 mergeState 输出机械派生,全系统只剩一份手写清单)、孤儿内容随每次 ingest 计划同事务写 sessions.orphans_json(∪ 合并,LazyBlockContents 感知)、full 模式钉死「索引持记录即服务,双载体皆坏才拒绝」、正则换 data-URL 载荷为 CAS 标记(字符串形状保留,读点零改动)、归零即删(UPDATE 守卫 + 条件 DELETE,FK 纪律五处重排为先删引用行再释放)。
    5. 是否有回归问题:审过的既有行为——envelope 字节稳定(mergeState 字面量逐字迁移,顺序未动)✓;reapBlocks 台账保留幂等 ✓;gc/deleteSession/archiveSession/checkpoint 兜底清扫保留 ✓;hydrateMedia 缺行回落可见不抛 ✓;proxy-persist/issue2129 四测试钉 legacy 后守卫语义不变 ✓。PR 自身引入两处回归(golden 错误重生成、mysql 车道 import 断裂)——已在上面三个标注 commit 修掉,等新一轮 CI 确认。
    6. 是否破坏缓存:无缓存语义变更;kstate_json 在新写入中键序变化(latch 家族移到对象尾部,纯内部列、按名读取、无字节钉)——旧行不受影响,digest 两侧共用同一清单后反而消除了既有顺序漂移。
    7. 是否改了设计方向:是,均为 issue 定案方向——full 模式权威语义成文;writerOff JSON 安全阀删除(契约无需逃生舱即成立);reap 日志从「调用点增量合并」变为「每次计划携带权威 ∪ 合并」。
    8. 是否引入新配置:无。无新 env/config/CLI。

    合并判定:两个回归修复已 push(311c7968e..1fcc87f8b),等这轮 CI 转绿即可合;缺口 C(裸 base64 载体)你拍板后我在同分支补或另开 follow-up。合并后建议把 #4011/#4075/#4218 三个被取代的 PR 按 duplicate 流程关闭(带说明评论)。

  5. ranxianglei commented on Oct 22, 2026

    @ranxianglei
    OwnerAuthor

    🤖 Powered by ework · qwen3.8-27b

    (ai: vllm-qwen/qwen3.8-27b) 上轮 review 的 mysql-contract 红灯第二跳已修并推上 PR 分支(175feab00):

    • 根因:state-hydration.ts 还从 session.js 导入 scrubTurnSeparatorIds,而 session.ts 顶部 value-imports acp-kernel——seam 图仍深一层够到未构建包(CI 报错从 "imported from state-hydration.js" 移到 "imported from src/session.ts",同一错误类)。
    • 修法:三个 [Bug][Responses] acp_turn_sep 合成分隔符进入持久压缩覆盖集合,导致重复历史漂移误报 #2627 turn-separator helper(零依赖)移入新叶子模块 src/turn-separator.ts;session.ts 保留 re-export(消费方 server/stream/fold-reconcile/tests 零改动);state-hydration 改从叶子导入。文档注释随代码原样迁移。
    • 验证:hermetic 复现——本地删掉 kernel/dist 后跑两个 contract 套件 = 11/11 绿(精确模拟该 lane 的 --ignore-scripts 环境);issue2627/state-fields/full-flip/orphan-representable 24/24;tsc 与 unused-export 门均绿。

    等这一跑的 mysql-contract 结果;绿了则全部 lane 齐绿。

  6. ranxianglei commented on Oct 22, 2026

    @ranxianglei
    OwnerAuthor

    🤖 Powered by ework · qwen3.8-27b

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

    收口状态:windows 分片红灯已定性为环境性时序 flake(非本 PR 回归),加固小修已在分支上,新一轮全矩阵正在跑。

    windows 分片二红的定性(按「同签名再红即升级」的预案走的)

    失败点固定:tests/wire-exit-matrix.test.ts abort 行的 SSE cell,断言 upstream socket(s) destroyed after client abort (closed=0)。证据链:

    1. 测试自身就是竞态:三个 SSE cell 用固定 setTimeout(300) 等上游 socket 销毁后直接断言;同一文件的 json cell 早就改成了轮询写法(30×100ms,注释明说「slow runners destroy the socket later」)——四个 cell 里只漏转了这三个。慢 runner 上 300ms 不够就红,与代码无关。
    2. 零 diff 重叠:该测试走的是 proxy 请求管道的 abort→destroy 路径(src/server.ts/loop),本 PR 的 diff 全部在存储层(persist/state-hydration/storage/*)+ 一个零行为 re-export(session.ts),不触碰任何相关路径;测试文件本身也不在 PR diff 里。
    3. 本地定向 3 次全绿(7/7×3),CI 两次红都在同一时间窗的 windows-latest/node22。
    4. 同窗全仓红风暴佐证环境不稳:03:00–03:30Z 窗口内,master 自己的 schedule run(c87185a)红在 e2e Context Window Overflow Issue Summary: Raw Requests Sent Directly to Upstream Rejected + Local Intercept Cooldown Lockin | 上下文超窗问题汇总:请求裸冲上游被拒 + 本地拦截冷却锁死会话(统一分析入口) #4079 两条;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 上被覆盖验证。

    结构化八问(最终态)

    1. 是什么问题:五个存储缺陷收口(字段清单漂移丢字段 / 孤儿形状冻结入库 / full 权威未定 / 媒体双拷贝 / CAS 零引用行只增不减)。
    2. 什么原因导致的:手抄两份字段清单且顺序也漂移;reap 形状索引不可表示导致早退冻结;加载路径权威未定案;splitMediaContent 只拆顶层槽位;releaseContent 只减不删。
    3. 影响范围与严重程度:p1 两处([Bug][storage] Retained orphan originals freeze unified session ingestion; cold loads restore hours-old state #4216 冷加载回小时级旧态、[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 真实库 11.32GB 零引用累积);其余为成本与潜在正确性。
    4. 怎么修复的:清单从 mergeState 输出机械派生(全系统一份手写);孤儿内容随每次 ingest 计划同事务写 orphans_json(∪ 合并);full 模式钉「索引持记录即服务,双载体皆坏才拒绝」;data-URL 载荷换 CAS 标记(字符串形状保留);归零即删(FK 纪律五处重排)。
    5. 是否有回归问题:PR 自身引入的两处回归已修并验证;既有行为逐项审过(envelope 字节稳定、reapBlocks 幂等保留、gc/archive/checkpoint 兜底保留、hydrateMedia 缺行可见回落、legacy 守卫语义不变)✓;本轮新增的 windows 红已定性为测试自身时序竞态+环境负载,与 diff 无因果,加固已落分支。
    6. 是否破坏缓存:无;kstate_json 新写入键序变化(内部列、按名读、无字节钉),digest 两侧共用同一清单反而消除了既有顺序漂移。
    7. 是否改了设计方向:均为 issue 定案方向(full 权威成文、writerOff 安全阀删除、reap 日志改权威 ∪ 合并)。
    8. 是否引入新配置:无。

    遗留两件事等你:①缺口 C(裸 base64 载体 anthropic source.data / google inlineData.data 未拆,纯成本、无正确性影响)——选 ① 我在同分支补,或 ② 写进 PR 描述接受留后续;②合入后我把 #4011/#4075/#4218 三个被取代 PR 按 duplicate 流程关闭(先评论说明再关)。

  7. ranxianglei commented on Oct 22, 2026

    @ranxianglei
    OwnerAuthor

    🤖 Powered by ework · qwen3.8-27b

    (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(先评论说明再关)。

  8. added a commit that references this issue on Oct 22, 2026
  9. ranxianglei commented on Oct 24, 2026

    @ranxianglei
    OwnerAuthor

    存储收尾第三波全部随 PR #4395(row-identity-historian)合并交付:

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

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions