fix: dedupe byte-identical durable event appends without consuming seq - #527
Merged
Conversation
LeXwDeX
force-pushed
the
fix/523-event-idempotency-gate
branch
from
September 3, 2026 06:24
c03ee70 to
772ea03
Compare
LeXwDeX
marked this pull request as ready for review
September 3, 2026 06:24
5 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #523
Why
481e31e2b7闸住 summary.diffs 之后,事件持久化浪费的主因是会话内逐字节重复写入:packages/core/src/event.ts的.insert(EventTable)(全仓唯一事件写入点)零重复检测。修复后数据实测(issue #523):message.updated.128,059 条 / 208.9 MiB 中 64.3%(134.3 MiB)是逐字节重复;ses_fe5fecc1e45c平均每条消息被重发 3.9 次全量快照,信息增量为零。What changed
EventTable新增data_hash列(nullable text,sha256 of 序列化 payload,append 时算一次落列,比对不重算 MiB 级 payload)。Legacy 行 hash 为 NULL 永不匹配 → 零回填:ALTER TABLE event ADD data_hash text是 O(1) 元数据操作,对生产 8.2 GiB event 表无启动负担commitDurableEventInnerfresh-append 路径):经event_aggregate_type_seq_idx定位同aggregate_id+type的上一条事件,data_hash相同 →return undefined= 完全静默(语义 B,已确认):不占 seq、不跑 projectors、不调 commit hook、不 wake durable 流。publisher 收到 unstamped payload —— 与单 publish 既有 unstamped 分支一致;session/input.tsadmit 已有catchDefect → find(stored)恢复路径,行为闭环replayAll(event.ts:574-585)断言重放序列连续(start+index),A 语义(占号留空洞)会让跨实例 sync 重放直接 diepublishMany对齐修复:results 改为与 entries 按位对齐(原if (result) push+ 按 index 配对在去重产生 undefined 时会给后续 payload 错标 seq);被去重 entry 仍 notify(unstamped),与单 publish 路径镜像sync.tshistory 显式列选择:data_hash不出机器,/sync/history响应形状不变 → 无 SDK 再生成、无 httpapi-exercise 变更20260903053128_add_event_data_hash+schema.gen.ts/migration.gen.ts/schema.json再生成(--check干净)Evidence
新增 9 个单测(
packages/core/test/event.test.ts)钉住验收与 B 语义:publishMany批内重复 → payload 对齐钉住([a→0, a→unstamped, b→1])gt(seq, after)路径)回归与门禁:
event+event-batch67 pass;core 回归 159 pass(control-plane/workspace、session-create/projector/prompt/runner/hotpath、database-migration、legacy-event-schema)test/session/全目录)bun run test:dag-core60 pass + 覆盖率地板全过(DAG 持久化骑在事件层上)bun typecheck(core + opencode)exit 0;root lint 4839 = dev 基线零新增readAfter、sync.tsreplay、workspace.ts均为显式字段映射,新列不外泄message.updated.1distinct/total → 1(post-fix 基线 0.78);生产库实测需已装 binary 更新后另行验证,不在本 PR 范围Checklist
specgit finishexits 0.