## 现象 PR #5757 的自查发现(host-plugin-notices.json 迁移到 sqlite 时):升级通知回执的 kv 迁移存在一个「先 dismiss、后 hydration」的时间窗问题——`src/notice-store.ts`(#5548 引入)里 `addDismissedKeyInMemory` 会惰性创建缓存,而 `hydrate` 顶部是 `if (cache !== null || hydrating) return`: - 如果进程启动期间有一次 dismiss/receipt 操作抢在 cli boot hydration 之前落地,hydration 会直接 bail——该进程的 legacy JSON → kv **一次性迁移被永久跳过**(每次启动文件都要重导),且 sibling lane 已写入的行在本进程内不可见; - 变体:dismissal 恰好落在 hydrate 的 await 间隙时,会被最后的 `cache = merged` 整体覆盖,直到重启。 #5757 已用同一模块 host-plugin-notice.ts 验证并修复了这个模式(union 进已有 lazy cache + 独立的 hydrated latch,见 commit 4d74613)。notice-store 是同一套模板的双胞胎,缺同样的修复。 ## 影响 回执数据本身是 advisory(丢一次只是多推一条升级通知),所以定级不高;但 #5548 之后的「legacy-only instance / 一次性导入」载具语义在窗口内名不副实。 ## 建议修法(同 #5757) `hydrate` 不再因 cache 非空 bail,改为把 kv rows union 进现有 cache;加独立 `hydrated` latch(区别于 lazy-init cache);final assignment 前合并 hydrate 期间新增的 key。复用 tests/host-plugin-unified-store.test.ts 里刚加的回归用例形状即可。
Activity
ranxianglei commentedon Nov 5, 2026
(ai: vllm-qwen/qwen3.8-27b)
查重: 不重复。 open+closed 检索无同机制条目;与 #5757(六个 JSON sidecar 迁 kv,在途 PR)是「同模式、不同模块」的关系——#5757 修的是 host-plugin-notice.ts(commit 4d74613,尚未合入 master),本单是它的双胞胎 notice-store.ts 上缺的同一处修复,issue 正文已自述该关系。
审核三步(动手前):
src/notice-store.ts:90:if (cache !== null || hydrating || !unifiedReceiptsActive()) return;——addDismissedKeyInMemory(:128-132)首次 dismiss 会惰性建 cache,cli boot 的void hydrateNoticeReceipts()(src/cli.ts:965)若晚于任何一次回执落地即永久 bail,legacy JSON→kv 一次性迁移跳过。变体也成立:hydrate 内部 await 间隙落地的 dismiss 会被 :120 的最终cache = merged整体覆盖。两点实现说明(与 #5757 的差异,均有意):
kvStore()探针尚未合入 master,本 PR 基于 origin/master 用既有的unifiedKvActive()+getStore()实现,不依赖在途 PR,[PR] storage: move six JSON sidecar states onto the unified sqlite store (#5753) #5757 先合后合都不冲突(对方分支不触碰 notice-store.ts 及其测试文件);标签: bug / scope/storage / p3(缺陷;统一存储层;回执是 advisory 数据、触发窗口窄——启动期一次 dismiss 抢跑 hydration,影响是多推一条通知+每次启动重导文件,定级不高与正文一致)。
接下来:基于 origin/master 开分支实现 + 两个回归用例(复用 #5757 新增用例形状:pre-hydration dismiss 不挡迁移 / hydrate await 间隙的 dismiss 存活),定向测试过后进 PR(Closes 上游 #5761)。
ranxianglei commentedon Nov 5, 2026
(ai: vllm-qwen/qwen3.8-27b)
**修复完成 → PR: https://github.com/ranxianglei/billion-context/pull/5768**(分支
2026-11-06_notice-store-hydration-race,基于 origin/master,Closes #5761)八问:
cache = merged整体覆盖。hydrateNoticeReceipts把「lazy cache 已存在」当「已完成 hydration」裁决 bail(src/notice-store.ts:90旧门cache !== null || ...),而首次 dismiss 会惰性创建同一个 cache——两种状态混在一个信号里,缺独立 hydrated latch;最终赋值又排在全部 await 之后,间隙新增必被覆盖。hydratedlatch 区别于 lazy-init;live 视图快照移到 publish 紧前、中间零 await(单线程保证间隙新增存活)。基于 master 既有 API(unifiedKvActive()+getStore())实现,不依赖尚未合入的kvStore()探针;[PR] storage: move six JSON sidecar states onto the unified sqlite store (#5753) #5757 分支不触碰这两个文件,先后合入均无冲突。回归用例有效性(A/B 实测):两个新用例复用 #5757 形状——pre-hydration dismiss 不挡迁移 / hydrate 间隙 dismiss 存活(后者对 list 和 set 双向加门,防止回执自身的持久化写在 list 解析前落地、经 kv 重推把丢失掩盖掉)。stash 掉修复后跑旧代码:两个新用例全红、原 5 绿;恢复修复后 7/7 绿。
等 CI 全局把关;CI 红了会按 #4599 协调流程处理。
ranxianglei commentedon Nov 5, 2026
(ai: vllm-qwen/qwen3.8-27b)
CI 全绿:PR #5768 共 30 项 check——test 全分片(ubuntu/windows × node 22/24 × 3 shard)、static-gates、kernel-guard、test-import-graph、pi-subagents-guard、docs-sync、mysql-contract 全部 success;skipped 均为环境门控的 e2e 车道(正常)。
收工判定:修复 + 回归钉已交付,CI 全局把关通过,等 owner review/merge(https://github.com/ranxianglei/billion-context/pull/5768)。与在途 #5757 无文件交集,合入顺序任意。
ranxianglei commentedon Nov 12, 2026
(已收)本单已归入根因聚合 issue(token 见聚合单 body),机器人已停止在本单上的工作,修复走聚合 issue 统一出 PR;本单保留不关,修复验证后统一关闭。(聚合单:[Root Cause] storage (6 items),GitHub #6453)