@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.
- package/dist/artifact.d.mts +44 -1
- package/dist/artifact.mjs +3 -3
- package/dist/{import-CKrzjXVB.mjs → import-eBSoCrxl.mjs} +1 -1
- package/dist/import.d.mts +1 -1
- package/dist/import.mjs +1 -1
- package/dist/{index-CgGDHQSK.d.mts → index-DfS0PzGM.d.mts} +17 -3
- package/dist/index.d.mts +1 -1
- package/dist/index.mjs +105 -24
- package/dist/{log-DTJsxMud.mjs → log-BIuXv09P.mjs} +215 -12
- package/dist/{sqlite-DCy4VTD8.mjs → sqlite-Dpd17YhI.mjs} +1 -1
- package/dist/storage.mjs +1 -1
- package/dist/testing.mjs +4 -4
- package/package.json +12 -12
- package/src/index.ts +96 -33
- package/src/legacy.ts +74 -13
- package/src/log.ts +254 -13
- package/src/testing/contract.ts +1 -1
package/dist/artifact.d.mts
CHANGED
|
@@ -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
|
|
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-
|
|
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 {
|
|
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-
|
|
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-
|
|
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
|
|
186
|
-
*
|
|
187
|
-
*
|
|
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-
|
|
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-
|
|
2
|
-
import {
|
|
3
|
-
import { c as SessionBranchRdbProvider, l as locateTurnEnd, o as registerSessionImport, s as SessionBranchRdb } from "./import-
|
|
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
|
-
|
|
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:
|
|
192
|
+
data: full ? stored["data"] : stored,
|
|
185
193
|
...row.fSurfaceOp !== null ? { surfaceOp: JSON.parse(row.fSurfaceOp) } : {}
|
|
186
194
|
};
|
|
187
195
|
}
|
|
188
196
|
/**
|
|
189
|
-
*
|
|
190
|
-
* @param row - 会话行(f_version <
|
|
197
|
+
* 把非当前格式(v0/v1/v2)行转换为当前逻辑事件与 header。
|
|
198
|
+
* @param row - 会话行(f_version < SESSION_FORMAT_VERSION)。
|
|
191
199
|
* @param eventRows - 已扫描保留的桥接行(稠密 seq,torn tail 已剔除)。
|
|
192
|
-
* @returns
|
|
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
|
-
/**
|
|
216
|
+
/** 会话行是否携带非当前格式(f_version < SESSION_FORMAT_VERSION)数据。 */
|
|
209
217
|
function isLegacyVersion(version) {
|
|
210
|
-
return version <
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
658
|
-
*
|
|
659
|
-
*
|
|
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");
|