@morlay/session-rdb 0.0.16-alpha.0 → 0.0.16-alpha.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -45,8 +45,51 @@ declare function findSurfaceRepairs(events: readonly SessionEvent[]): {
45
45
  degradeToAppend: Set<number>;
46
46
  addAppendMarker: Set<number>;
47
47
  clearSurfaceOp: Set<number>;
48
+ clampEnd: Map<number, number>;
48
49
  };
49
50
  declare function repairSurfaceOps(events: SessionEvent[]): void;
51
+ /**
52
+ * 读取时补全 assistant 结算字段:旧写入器不落库 `stream`(v2 才把流式记录
53
+ * 嵌入消息),读回时缺失即补空数组——上游 seed 校验要求 turn/step/stream
54
+ * 三者齐备,缺失会让整个会话加载失败。
55
+ */
56
+ declare function repairAssistantSettlement(events: SessionEvent[]): void;
57
+ /**
58
+ * 读取时把旧格式 `request/header` 归一为当前格式:v3 起 system prompt 由
59
+ * surface 上的 `system/message` 承载,header 必须省略 `system`,空 `tools` /
60
+ * `adapterDefaults` 也必须省略(上游 v3 事件校验 fail loud)。混合世代回退
61
+ * 视图直接采用存储行,不归一会让整个会话加载失败。system prompt 因此不再
62
+ * 进入模型请求;会话下次运行由 v3 的 system/message 机制重建。
63
+ */
64
+ declare function repairRequestHeaders(events: SessionEvent[]): void;
65
+ /**
66
+ * 把紧邻 replace 的 metering 事件的 `shadowedRange` / `shadowedSeqs` 对齐到
67
+ * replace 最终的稠密 range。
68
+ *
69
+ * 旧写入器重编号事件后,metering 与 replace 一起落在旧坐标空间:二者数值
70
+ * 相等但与当前 surface 无关。夹取(或 range 本身已落在稠密空间而 metering
71
+ * 仍是旧值)后二者不再相等,而上游 token-meter 契约要求紧邻的 metering
72
+ * range 与 replace range 完全一致(否则报 no adjacent shadow price)。range
73
+ * 已一致时不改写——当前写入器保证一致,shadowedSeqs 的额外并发节点因此
74
+ * 原样保留。
75
+ */
76
+ declare function syncMeteringRanges(events: SessionEvent[]): void;
77
+ /**
78
+ * 读取时把 v2 时代的 PTC 词汇归一为当前词汇。
79
+ *
80
+ * 上游 v2→v3 迁移把 `tool/code-dispatch(-start)` 改名为
81
+ * `tool/ptc-dispatch(-start)`、把 `tools-code-mode` 插件来源改名为 `tools-ptc`、
82
+ * 把 agent preset 值 `code` 改名为 `ptc`。混合世代回退视图直接采用存储行,
83
+ * 不重命名会被上游 v3 校验以「unknown event type」拒绝整个会话。词汇改写
84
+ * 与上游迁移链的 renamePtcEvent 对齐。
85
+ */
86
+ declare function renameLegacyPtcEvents(events: SessionEvent[]): void;
87
+ /**
88
+ * 读取视图修复总入口:形状补全(结算字段 / request header)→ PTC 词汇归一
89
+ * → surface 语义修复 → metering 对齐 → provenance 重算 → 孤儿 inbox splice
90
+ * 改写。全部只作用于内存视图,不落库。
91
+ */
92
+ declare function repairReadView(events: SessionEvent[]): void;
50
93
  /**
51
94
  * 定位无法从空状态增量重放的 agent/inbox/spliced 事件(孤儿操作)。
52
95
  *
@@ -64,4 +107,4 @@ declare function scanRows(rows: readonly EventRow[], base?: number): {
64
107
  };
65
108
  declare function toJsonlArtifact(meta: SessionHeader, inheritedEventCount: number, events: readonly SessionEvent[]): string;
66
109
  //#endregion
67
- export { SESSION_IMPORT_PATH, SESSION_LOG_ARTIFACT_FILENAME, findSurfaceRepairs, orphanInboxSpliceSeqs, parseImportZip, parseJsonlArtifact, persistImport, recomputeReplaceProvenance, registerSessionImport, repairOrphanInboxSplices, repairSurfaceOps, rowToEvent, rowToMeta, scanRows, sessionConflictRow, sessionInsertRow, toJsonlArtifact };
110
+ export { SESSION_IMPORT_PATH, SESSION_LOG_ARTIFACT_FILENAME, findSurfaceRepairs, orphanInboxSpliceSeqs, parseImportZip, parseJsonlArtifact, persistImport, recomputeReplaceProvenance, registerSessionImport, renameLegacyPtcEvents, repairAssistantSettlement, repairOrphanInboxSplices, repairReadView, repairRequestHeaders, repairSurfaceOps, rowToEvent, rowToMeta, scanRows, sessionConflictRow, sessionInsertRow, syncMeteringRanges, toJsonlArtifact };
package/dist/artifact.mjs CHANGED
@@ -1,3 +1,3 @@
1
- import { a as repairSurfaceOps, c as scanRows, d as toJsonlArtifact, i as repairOrphanInboxSplices, l as sessionConflictRow, n as orphanInboxSpliceSeqs, o as rowToEvent, r as recomputeReplaceProvenance, s as rowToMeta, t as findSurfaceRepairs, u as sessionInsertRow } from "./log-DTJsxMud.mjs";
2
- import { a as persistImport, i as parseJsonlArtifact, n as SESSION_LOG_ARTIFACT_FILENAME, o as registerSessionImport, r as parseImportZip, t as SESSION_IMPORT_PATH } from "./import-CKrzjXVB.mjs";
3
- export { SESSION_IMPORT_PATH, SESSION_LOG_ARTIFACT_FILENAME, findSurfaceRepairs, orphanInboxSpliceSeqs, parseImportZip, parseJsonlArtifact, persistImport, recomputeReplaceProvenance, registerSessionImport, repairOrphanInboxSplices, repairSurfaceOps, rowToEvent, rowToMeta, scanRows, sessionConflictRow, sessionInsertRow, toJsonlArtifact };
1
+ import { a as repairAssistantSettlement, c as repairRequestHeaders, d as rowToMeta, f as scanRows, g as toJsonlArtifact, h as syncMeteringRanges, i as renameLegacyPtcEvents, l as repairSurfaceOps, m as sessionInsertRow, n as orphanInboxSpliceSeqs, o as repairOrphanInboxSplices, p as sessionConflictRow, r as recomputeReplaceProvenance, s as repairReadView, t as findSurfaceRepairs, u as rowToEvent } from "./log-BIuXv09P.mjs";
2
+ import { a as persistImport, i as parseJsonlArtifact, n as SESSION_LOG_ARTIFACT_FILENAME, o as registerSessionImport, r as parseImportZip, t as SESSION_IMPORT_PATH } from "./import-eBSoCrxl.mjs";
3
+ export { SESSION_IMPORT_PATH, SESSION_LOG_ARTIFACT_FILENAME, findSurfaceRepairs, orphanInboxSpliceSeqs, parseImportZip, parseJsonlArtifact, persistImport, recomputeReplaceProvenance, registerSessionImport, renameLegacyPtcEvents, repairAssistantSettlement, repairOrphanInboxSplices, repairReadView, repairRequestHeaders, repairSurfaceOps, rowToEvent, rowToMeta, scanRows, sessionConflictRow, sessionInsertRow, syncMeteringRanges, toJsonlArtifact };
@@ -1,4 +1,4 @@
1
- import { s as rowToMeta } from "./log-DTJsxMud.mjs";
1
+ import { d as rowToMeta } from "./log-BIuXv09P.mjs";
2
2
  import { randomUUID } from "node:crypto";
3
3
  import { SESSION_FORMAT_VERSION, SessionLogOffset } from "@deepseek-ai/dsh-session";
4
4
  import { sessionFormatCatalog } from "@deepseek-ai/dsh-session-format-catalog";
package/dist/import.d.mts CHANGED
@@ -1,4 +1,4 @@
1
- import { n as SessionPersistenceRdb } from "./index-CgGDHQSK.mjs";
1
+ import { n as SessionPersistenceRdb } from "./index-DfS0PzGM.mjs";
2
2
  import { SessionStorageMetadata } from "@deepseek-ai/dsh-session-persistence";
3
3
  import { Session, SessionEvent, SessionId } from "@deepseek-ai/dsh-session";
4
4
  import { Context } from "@deepseek-ai/cordis";
package/dist/import.mjs CHANGED
@@ -1,2 +1,2 @@
1
- import { a as persistImport, i as parseJsonlArtifact, n as SESSION_LOG_ARTIFACT_FILENAME, o as registerSessionImport, r as parseImportZip, t as SESSION_IMPORT_PATH } from "./import-CKrzjXVB.mjs";
1
+ import { a as persistImport, i as parseJsonlArtifact, n as SESSION_LOG_ARTIFACT_FILENAME, o as registerSessionImport, r as parseImportZip, t as SESSION_IMPORT_PATH } from "./import-eBSoCrxl.mjs";
2
2
  export { SESSION_IMPORT_PATH, SESSION_LOG_ARTIFACT_FILENAME, parseImportZip, parseJsonlArtifact, persistImport, registerSessionImport };
@@ -182,9 +182,9 @@ declare class SessionPersistenceRdb extends SessionPersistence {
182
182
  /** 空会话 materialize(flush 的持久化屏障)。 */
183
183
  materializeEmpty(meta: SessionHeader, inheritedEventCount: SessionLogOffset): Promise<void>;
184
184
  /** 原样 append 落库(handle 已校验 contiguity;torn tail 先截断)。
185
- * 与上游 JSONL 一致:ignorable 事件原样存储,不做过滤。写路径校验 v2
186
- * 形状(fail-closed):未知类型(非 ignorable)与非法消息形状拒绝入库;
187
- * 旧格式(v0/v1)数据只在读取时经 legacy 转换链动态转换,不落新库。 */
185
+ * 与上游 JSONL 一致:ignorable 事件原样存储,不做过滤。写路径校验当前
186
+ * 格式形状(fail-closed):未知类型(非 ignorable)与非法消息形状拒绝入库;
187
+ * 非当前格式(v0/v1/v2)数据只在读取时经 legacy 转换链动态转换,不落新库。 */
188
188
  appendBatch(meta: SessionHeader, inheritedEventCount: SessionLogOffset, events: readonly SessionEvent[], tornTruncateTo?: number): Promise<boolean>;
189
189
  /** 读取一个会话的稠密 log(含 torn tail 检测,不含修复改写)。 */
190
190
  readLog(id: SessionId, options?: {
@@ -196,7 +196,21 @@ declare class SessionPersistenceRdb extends SessionPersistence {
196
196
  tornFrom?: number;
197
197
  incarnation: string;
198
198
  revision: number;
199
+ /** 存储桥接行数(迁移链可能生成/合并事件,与 `events.length` 不同)。 */
200
+ storedCount: number;
201
+ /** 是否经上游迁移链转换(非当前格式 → 当前格式)。 */
202
+ migrated: boolean;
199
203
  } | undefined>;
204
+ /**
205
+ * 把迁移链读出的当前格式视图整体落库(非当前格式会话写打开时的一次性迁移)。
206
+ *
207
+ * 迁移链会生成/合并事件(end-seed / attempt / chunk 合并),事件 seq 空间
208
+ * 与存储桥接行数不再相等;写路径以存储 head 为锚点重编号,二者不一致会让
209
+ * append 撞上已有行。这里在同一事务内删光本会话桥接行、按迁移视图重建
210
+ * (新事件行,完整信封),并更新 head 与 revision;旧事件行保留(可能被
211
+ * fork 子会话引用,孤儿由惰性 GC 处理)。
212
+ */
213
+ private rewriteMigratedLog;
200
214
  listSnapshots(signal?: AbortSignal): Promise<Array<SessionPersistenceSnapshot & {
201
215
  inheritedEventCount: number;
202
216
  }>>;
package/dist/index.d.mts CHANGED
@@ -1,3 +1,3 @@
1
1
  import { o as SCHEMA_VERSION } from "./schema-COK7-wRV.mjs";
2
- import { a as SessionBranchRdbProvider, i as SessionBranchRdb, n as SessionPersistenceRdb, o as locateTurnEnd, r as SessionPersistenceRdbInternals, t as Config } from "./index-CgGDHQSK.mjs";
2
+ import { a as SessionBranchRdbProvider, i as SessionBranchRdb, n as SessionPersistenceRdb, o as locateTurnEnd, r as SessionPersistenceRdbInternals, t as Config } from "./index-DfS0PzGM.mjs";
3
3
  export { Config, SCHEMA_VERSION, SessionBranchRdb, SessionBranchRdbProvider, SessionPersistenceRdb, SessionPersistenceRdb as default, SessionPersistenceRdbInternals, locateTurnEnd };
package/dist/index.mjs CHANGED
@@ -1,12 +1,12 @@
1
- import { a as SCHEMA_VERSION, g as WriteGuard, h as toPostgresSchema, i as EVENT_ENCODING, m as postgresTableDefs, r as DEFAULT_BUSY_TIMEOUT_MS, s as eventDimensions, t as SqliteBackend } from "./sqlite-DCy4VTD8.mjs";
2
- import { a as repairSurfaceOps, c as scanRows, d as toJsonlArtifact, i as repairOrphanInboxSplices, l as sessionConflictRow, r as recomputeReplaceProvenance, s as rowToMeta, u as sessionInsertRow } from "./log-DTJsxMud.mjs";
3
- import { c as SessionBranchRdbProvider, l as locateTurnEnd, o as registerSessionImport, s as SessionBranchRdb } from "./import-CKrzjXVB.mjs";
1
+ import { a as SCHEMA_VERSION, g as WriteGuard, h as toPostgresSchema, i as EVENT_ENCODING, m as postgresTableDefs, r as DEFAULT_BUSY_TIMEOUT_MS, s as eventDimensions, t as SqliteBackend } from "./sqlite-Dpd17YhI.mjs";
2
+ import { c as repairRequestHeaders, d as rowToMeta, f as scanRows, g as toJsonlArtifact, m as sessionInsertRow, p as sessionConflictRow, s as repairReadView } from "./log-BIuXv09P.mjs";
3
+ import { c as SessionBranchRdbProvider, l as locateTurnEnd, o as registerSessionImport, s as SessionBranchRdb } from "./import-eBSoCrxl.mjs";
4
4
  import z from "@deepseek-ai/schemastery";
5
5
  import { randomUUID } from "node:crypto";
6
6
  import { Pool } from "pg";
7
7
  import { drizzle } from "drizzle-orm/node-postgres";
8
8
  import { SessionAlreadyExistsError, SessionAlreadyOwnedError, SessionHandleClosedError, SessionPersistence, SessionPersistenceNotFoundError, SessionPersistenceRevision, SessionReadOnlyError, assertContiguous, assertVersion, materializeAppendBatch, materializeCreateHeader, validateStoredEvents } from "@deepseek-ai/dsh-session-persistence";
9
- import { SessionLogOffset } from "@deepseek-ai/dsh-session";
9
+ import { SESSION_FORMAT_VERSION, SessionLogOffset } from "@deepseek-ai/dsh-session";
10
10
  import { sessionFormatCatalog } from "@deepseek-ai/dsh-session-format-catalog";
11
11
  import { and, eq, gte, sql } from "drizzle-orm";
12
12
  import { readdirSync } from "node:fs";
@@ -161,35 +161,43 @@ var PostgresBackend = class PostgresBackend {
161
161
  };
162
162
  //#endregion
163
163
  //#region src/legacy.ts
164
- /** 重建 v0/v1 物理 header 记录(RDB 行 → 物理 JSON 对象)。 */
164
+ /** 重建 v0/v1/v2 物理 header 记录(RDB 行 → 物理 JSON 对象)。 */
165
165
  function physicalHeader(row) {
166
- return {
166
+ const common = {
167
167
  type: "session",
168
168
  version: row.fVersion,
169
169
  id: row.fSessionId,
170
170
  createdAt: row.fCreatedAt,
171
171
  ...row.fCwd !== null ? { cwd: row.fCwd } : {},
172
172
  ...row.fParentSession !== null ? { parentSession: row.fParentSession } : {},
173
- ...row.fSeedLength !== null ? { seedLength: row.fSeedLength } : {},
174
173
  ...row.fOrigin !== null ? { origin: row.fOrigin } : {},
175
174
  delegationDepth: row.fDelegationDepth ?? 0
176
175
  };
176
+ return row.fVersion < 2 ? {
177
+ ...common,
178
+ ...row.fSeedLength !== null ? { seedLength: row.fSeedLength } : {}
179
+ } : {
180
+ ...common,
181
+ isSeeded: row.fSeedLength !== null
182
+ };
177
183
  }
178
- /** 重建一条 v0/v1 物理事件记录(桥接行 + 事件行 → 物理 JSON 对象)。 */
184
+ /** 重建一条 v0/v1/v2 物理事件记录(桥接行 + 事件行 → 物理 JSON 对象)。 */
179
185
  function physicalEvent(row) {
186
+ const stored = JSON.parse(row.fData);
187
+ const full = typeof stored === "object" && stored !== null && !Array.isArray(stored) && typeof stored["type"] === "string" && "data" in stored;
180
188
  return {
181
189
  type: row.fType,
182
190
  seq: row.fSequence,
183
191
  time: row.fCreatedAt,
184
- data: JSON.parse(row.fData),
192
+ data: full ? stored["data"] : stored,
185
193
  ...row.fSurfaceOp !== null ? { surfaceOp: JSON.parse(row.fSurfaceOp) } : {}
186
194
  };
187
195
  }
188
196
  /**
189
- * 把旧格式(v0/v1)行转换为 v2 逻辑事件与 header。
190
- * @param row - 会话行(f_version < 2)。
197
+ * 把非当前格式(v0/v1/v2)行转换为当前逻辑事件与 header。
198
+ * @param row - 会话行(f_version < SESSION_FORMAT_VERSION)。
191
199
  * @param eventRows - 已扫描保留的桥接行(稠密 seq,torn tail 已剔除)。
192
- * @returns v2 逻辑 header、继承前缀长度与事件。
200
+ * @returns 当前逻辑 header、继承前缀长度与事件。
193
201
  * @throws 迁移链拒绝(格式损坏 / 无法迁移)时原样抛出。
194
202
  */
195
203
  function convertLegacyRows(row, eventRows) {
@@ -205,9 +213,40 @@ function convertLegacyRows(row, eventRows) {
205
213
  events: artifact.events
206
214
  };
207
215
  }
208
- /** 会话行是否携带旧格式(v0/v1)数据。 */
216
+ /** 会话行是否携带非当前格式(f_version < SESSION_FORMAT_VERSION)数据。 */
209
217
  function isLegacyVersion(version) {
210
- return version < 2;
218
+ return version < SESSION_FORMAT_VERSION;
219
+ }
220
+ /**
221
+ * 迁移链拒绝后,把旧格式行直接当**当前格式**事件视图采用。
222
+ *
223
+ * 旧写入器只在建会话时落一次 header version,之后跟随上游演进继续追加
224
+ * 事件——同一个 log 因此可能是混合世代(v0 时代的行 + 新版本字段),不是
225
+ * 任何单一已发布格式,严格迁移链必然拒绝。这些行的数据形状(消息包装、
226
+ * 类型名)已经是当前格式,只需 header 版本归一到当前、按稠密 seq 读取;
227
+ * 事件数据的补全与 surface 修复由读取视图修复(repairReadView)负责。
228
+ *
229
+ * 调用方须在此之后执行 `validateStoredEvents`:真正属于旧格式的类型
230
+ * (`assistant/chunk` / `compact/*` 等)仍会被 fail-loud 拒绝。
231
+ *
232
+ * @param row - 会话行(f_version < SESSION_FORMAT_VERSION)。
233
+ * @param eventRows - 该会话全部桥接行(按 f_sequence 升序)。
234
+ * @returns 当前格式 header(version 归一到 SESSION_FORMAT_VERSION)、收缩后的
235
+ * 继承前缀长度、稠密事件与可选 torn tail 起点。
236
+ * @throws scanRows 的已提交区损坏错误(seq gap / 不可解析行)。
237
+ */
238
+ function adoptLegacyRows(row, eventRows) {
239
+ const { preserved, tornFrom } = scanRows(eventRows, 0);
240
+ repairRequestHeaders(preserved);
241
+ return {
242
+ meta: {
243
+ ...rowToMeta(row),
244
+ version: SESSION_FORMAT_VERSION
245
+ },
246
+ inheritedEventCount: Math.min(row.fSeedLength ?? 0, preserved.length),
247
+ events: preserved,
248
+ ...tornFrom !== void 0 ? { tornFrom } : {}
249
+ };
211
250
  }
212
251
  //#endregion
213
252
  //#region src/index.ts
@@ -352,9 +391,7 @@ var RdbSessionHandle = class RdbSessionHandle {
352
391
  };
353
392
  throw new SessionPersistenceNotFoundError(this.id);
354
393
  }
355
- repairSurfaceOps(log.events);
356
- recomputeReplaceProvenance(log.events);
357
- repairOrphanInboxSplices(log.events);
394
+ repairReadView(log.events);
358
395
  return {
359
396
  eventState: "detached",
360
397
  events: log.events.slice(offset, offset + length)
@@ -550,6 +587,7 @@ var SessionPersistenceRdb = class SessionPersistenceRdb extends SessionPersisten
550
587
  }));
551
588
  const log = await this.readLog(id, {}, options?.signal);
552
589
  if (log === void 0) throw new SessionPersistenceNotFoundError(id);
590
+ repairReadView(log.events);
553
591
  validateStoredEvents(log.meta, log.events);
554
592
  return this.tracker.adopt(new RdbSessionHandle(this, id, log.meta, "read", {
555
593
  cursor: log.events.length,
@@ -566,7 +604,9 @@ var SessionPersistenceRdb = class SessionPersistenceRdb extends SessionPersisten
566
604
  }));
567
605
  const log = await this.readLog(id, {}, options?.signal);
568
606
  if (log === void 0) throw new SessionPersistenceNotFoundError(id);
607
+ repairReadView(log.events);
569
608
  validateStoredEvents(log.meta, log.events);
609
+ if (log.migrated && log.events.length !== log.storedCount) await this.rewriteMigratedLog(id, log);
570
610
  this.writeGuard.confirmHead(id, log.events.at(-1)?.seq ?? -1);
571
611
  return this.tracker.adopt(new RdbSessionHandle(this, id, log.meta, "write", {
572
612
  cursor: log.events.length,
@@ -630,8 +670,7 @@ var SessionPersistenceRdb = class SessionPersistenceRdb extends SessionPersisten
630
670
  signal?.throwIfAborted();
631
671
  const log = await this.readLog(id, {}, signal);
632
672
  if (log === void 0) return void 0;
633
- repairSurfaceOps(log.events);
634
- recomputeReplaceProvenance(log.events);
673
+ repairReadView(log.events);
635
674
  const inheritedEventCount = Math.min(log.inheritedEventCount, log.events.length);
636
675
  return {
637
676
  meta: log.meta,
@@ -654,9 +693,9 @@ var SessionPersistenceRdb = class SessionPersistenceRdb extends SessionPersisten
654
693
  this.writeGuard.confirmHead(meta.id, -1);
655
694
  }
656
695
  /** 原样 append 落库(handle 已校验 contiguity;torn tail 先截断)。
657
- * 与上游 JSONL 一致:ignorable 事件原样存储,不做过滤。写路径校验 v2
658
- * 形状(fail-closed):未知类型(非 ignorable)与非法消息形状拒绝入库;
659
- * 旧格式(v0/v1)数据只在读取时经 legacy 转换链动态转换,不落新库。 */
696
+ * 与上游 JSONL 一致:ignorable 事件原样存储,不做过滤。写路径校验当前
697
+ * 格式形状(fail-closed):未知类型(非 ignorable)与非法消息形状拒绝入库;
698
+ * 非当前格式(v0/v1/v2)数据只在读取时经 legacy 转换链动态转换,不落新库。 */
660
699
  async appendBatch(meta, inheritedEventCount, events, tornTruncateTo) {
661
700
  await this.ready;
662
701
  if (events.length === 0) return false;
@@ -699,14 +738,29 @@ var SessionPersistenceRdb = class SessionPersistenceRdb extends SessionPersisten
699
738
  const meta = rowToMeta(row);
700
739
  const eventRows = options.fromSeq === void 0 ? await this.backend.getEventRows(id) : await this.backend.getEventRows(id, options.fromSeq);
701
740
  signal?.throwIfAborted();
702
- if (isLegacyVersion(row.fVersion)) {
741
+ if (isLegacyVersion(row.fVersion)) try {
703
742
  const converted = convertLegacyRows(row, eventRows);
704
743
  return {
705
744
  meta: converted.meta,
706
745
  inheritedEventCount: converted.inheritedEventCount,
707
746
  events: converted.events,
708
747
  incarnation: row.fIncarnation,
709
- revision: row.fRevision
748
+ revision: row.fRevision,
749
+ storedCount: eventRows.length,
750
+ migrated: true
751
+ };
752
+ } catch (error) {
753
+ this.ctx.logger.warn(`session-rdb: session "${id}" is not a single released format; adopting its stored rows as current-format data (${error instanceof Error ? error.message : String(error)})`);
754
+ const adopted = adoptLegacyRows(row, eventRows);
755
+ return {
756
+ meta: adopted.meta,
757
+ inheritedEventCount: adopted.inheritedEventCount,
758
+ events: adopted.events,
759
+ incarnation: row.fIncarnation,
760
+ revision: row.fRevision,
761
+ storedCount: eventRows.length,
762
+ migrated: false,
763
+ ...adopted.tornFrom !== void 0 ? { tornFrom: adopted.tornFrom } : {}
710
764
  };
711
765
  }
712
766
  const { preserved, tornFrom } = scanRows(eventRows, options.fromSeq ?? 0);
@@ -716,9 +770,35 @@ var SessionPersistenceRdb = class SessionPersistenceRdb extends SessionPersisten
716
770
  events: preserved,
717
771
  incarnation: row.fIncarnation,
718
772
  revision: row.fRevision,
773
+ storedCount: eventRows.length,
774
+ migrated: false,
719
775
  ...tornFrom !== void 0 ? { tornFrom } : {}
720
776
  };
721
777
  }
778
+ /**
779
+ * 把迁移链读出的当前格式视图整体落库(非当前格式会话写打开时的一次性迁移)。
780
+ *
781
+ * 迁移链会生成/合并事件(end-seed / attempt / chunk 合并),事件 seq 空间
782
+ * 与存储桥接行数不再相等;写路径以存储 head 为锚点重编号,二者不一致会让
783
+ * append 撞上已有行。这里在同一事务内删光本会话桥接行、按迁移视图重建
784
+ * (新事件行,完整信封),并更新 head 与 revision;旧事件行保留(可能被
785
+ * fork 子会话引用,孤儿由惰性 GC 处理)。
786
+ */
787
+ async rewriteMigratedLog(id, log) {
788
+ await this.backend.transaction(async (tx) => {
789
+ await tx.deleteBridgeTail(id, 0);
790
+ await tx.upsertSession({
791
+ meta: log.meta,
792
+ inheritedEventCount: SessionLogOffset(log.inheritedEventCount)
793
+ }, randomUUID());
794
+ const { headEventId, headSequence } = await appendEventTail(tx, log.meta, log.events, {
795
+ parentId: "",
796
+ nextSeq: 0
797
+ });
798
+ await tx.updateHead(id, headEventId, headSequence);
799
+ await tx.bumpRevision(id);
800
+ });
801
+ }
722
802
  async listSnapshots(signal) {
723
803
  signal?.throwIfAborted();
724
804
  await this.ready;
@@ -904,6 +984,7 @@ var SessionPersistenceRdb = class SessionPersistenceRdb extends SessionPersisten
904
984
  if (stored.meta.cwd !== session.header.cwd) throw new Error(`session "${id}" is already persisted at a different cwd (persisted: ${String(stored.meta.cwd)}, live: ${String(session.header.cwd)}) (id collision)`);
905
985
  if (stored.inheritedEventCount !== session.inheritedEventCount) throw new Error(`session "${id}" is already persisted with a different inherited event count (id collision)`);
906
986
  assertVersion(stored.meta);
987
+ repairReadView(stored.events);
907
988
  const seed = session.snapshotEvents();
908
989
  if (!seedCoversPrefix(seed, stored.events)) throw new Error(`session "${id}" already has a persisted log on disk that does not match this live session (id collision)`);
909
990
  handle = await this.open(id, "write");