@morlay/session-rdb 0.0.10 → 0.0.11

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.
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.mts","names":[],"sources":["../src/backend.ts","../src/write-guard.ts","../src/schema.ts","../src/branch.ts","../src/index.ts"],"mappings":"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"}
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../src/backend.ts","../src/write-guard.ts","../src/schema.ts","../src/branch.ts","../src/index.ts"],"mappings":"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"}
package/lib/index.mjs CHANGED
@@ -14,7 +14,7 @@ import { dirname, resolve } from "node:path";
14
14
  import { DatabaseSync } from "node:sqlite";
15
15
  import { drizzle as drizzle$1 } from "drizzle-orm/node-sqlite";
16
16
  import { SESSION_FORMAT_VERSION } from "@deepseek-ai/dsh-session";
17
- import { SessionBranch, SessionBranchError, buildTimeline } from "@morlay/session-branch";
17
+ import { SessionBranch, SessionBranchError, balanceRewindPrefix, buildTimeline } from "@morlay/session-branch";
18
18
  //#region src/log.ts
19
19
  /**
20
20
  * Reconstruct the {@link SessionHeader} from a `t_sessions` row.
@@ -1583,7 +1583,8 @@ var SessionBranchRdbProvider = class {
1583
1583
  const boundaryEvent = events[toBoundary];
1584
1584
  if (toBoundary === -1) {} else if (boundaryEvent === void 0) throw new SessionBranchError(`rewind boundary ${toBoundary} does not exist in session "${id}"`, "INVALID_BOUNDARY");
1585
1585
  else if (boundaryEvent.type !== "turn/end" && boundaryEvent.type !== "user/message") throw new SessionBranchError(`rewind boundary ${toBoundary} is not a turn/end or user/message (${boundaryEvent.type})`, "INVALID_BOUNDARY");
1586
- const keepLength = toBoundary === -1 ? 0 : boundaryEvent.type === "turn/end" ? toBoundary + 1 : toBoundary;
1586
+ const rawKeepLength = toBoundary === -1 ? 0 : boundaryEvent.type === "turn/end" ? toBoundary + 1 : toBoundary;
1587
+ const keepLength = balanceRewindPrefix(events.slice(0, rawKeepLength)).length;
1587
1588
  const internals = this.persistence.internals();
1588
1589
  const denseBoundary = live === void 0 ? keepLength - 1 : events.slice(0, keepLength).filter(isPersistedEvent).length - 1;
1589
1590
  await internals.backend.transaction(async (tx) => {
package/lib/index.mjs.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":["buildColumn","sqliteCheck","text","integer","pgCheck","unique","index","drizzle","drizzlePg"],"sources":["../src/log.ts","../src/write-guard.ts","../src/entities/types.ts","../src/adapters/to-sqlite.ts","../src/adapters/to-postgres.ts","../src/adapters/ddl.ts","../src/entities/persistence-state.ts","../src/entities/schema-meta.ts","../src/entities/sessions.ts","../src/entities/events.ts","../src/entities/session-events.ts","../src/entities/index.ts","../src/schema.ts","../src/sqlite.ts","../src/postgres.ts","../src/branch.ts","../src/index.ts"],"sourcesContent":["/**\n * 会话 log 的表示转换:把持久化行(`t_sessions` / joined `t_session_events` +\n * `t_events` 行)转换为上游 `SessionHeader` / `SessionEvent`,以及反向的\n * 分类/映射辅助。全部为方言无关的纯函数,无 I/O——测试直接单测(见\n * `tests/rdb.spec.ts`)。\n *\n * delta 过滤(`assistant/chunk` 不落库)后,持久化 seq 是稠密重编号的;\n * `buildSeqMap` 提供 上游 seq → 持久化 seq 的映射,`rowToEvent` /\n * `remapSurfaceOp` / `remapShadowedRange` 经它把 `sourceEventSeqs`、\n * `replace` 范围与 compact 计量事件的 `shadowedRange` 重映射回稠密\n * seq 空间。`scanRows` 实现崩溃尾部语义(torn tail 切割 + 提交区损坏拒绝)。\n *\n * @module @morlay/session-rdb/log\n */\n\nimport type { SessionEvent, SessionHeader, SessionId, SurfaceOp } from \"@deepseek-ai/dsh-session\";\nimport type { EventRow, SessionRow } from \"./backend.ts\";\n\n/**\n * Reconstruct the {@link SessionHeader} from a `t_sessions` row.\n * @param row - the `t_sessions` table row.\n * @returns the header, `NULL` columns mapped to omitted optional fields.\n */\nexport function rowToMeta(row: SessionRow): SessionHeader {\n if (!Number.isSafeInteger(row.fCreatedAt) || row.fCreatedAt < 0) {\n throw new Error(\"stored session createdAt must be a non-negative safe integer\");\n }\n return {\n version: row.fVersion,\n id: row.fSessionId as SessionId,\n createdAt: row.fCreatedAt,\n ...(row.fCwd !== null ? { cwd: row.fCwd } : {}),\n ...(row.fParentSession !== null ? { parentSession: row.fParentSession as SessionId } : {}),\n ...(row.fSeedLength !== null ? { seedLength: row.fSeedLength } : {}),\n ...(row.fOrigin !== null ? { origin: row.fOrigin as \"subagent\" } : {}),\n ...(row.fDelegationDepth === null ? {} : { delegationDepth: row.fDelegationDepth }),\n };\n}\n\n/**\n * `t_sessions` 的 INSERT 列值:`SessionHeader` 的持久化字段 + 初始 head 游标\n * (空事件 id、seq -1)+ materialization identity(`f_incarnation`)+ revision 0。\n * 方言无关的纯映射——SQLite 与 PostgreSQL 后端的 `upsertSession` 共用,列名\n * 与 `src/entities/sessions.ts` 对齐(改一处即两方言生效)。`f_id` serial 由\n * 数据库生成,不在映射内。\n */\nexport function sessionInsertRow(\n meta: SessionHeader,\n incarnation: string,\n): {\n fSessionId: string;\n fHeadEventId: string;\n fHeadSequence: number;\n fVersion: number;\n fCreatedAt: number;\n fCwd: string | null;\n fParentSession: string | null;\n fSeedLength: number | null;\n fOrigin: string | null;\n fDelegationDepth: number | null;\n fIncarnation: string;\n fRevision: number;\n} {\n return {\n fSessionId: meta.id,\n fHeadEventId: \"\",\n fHeadSequence: -1,\n fVersion: meta.version,\n fCreatedAt: meta.createdAt,\n fCwd: meta.cwd ?? null,\n fParentSession: meta.parentSession ?? null,\n fSeedLength: meta.seedLength ?? null,\n fOrigin: meta.origin ?? null,\n fDelegationDepth: meta.delegationDepth ?? null,\n fIncarnation: incarnation,\n fRevision: 0,\n };\n}\n\n/**\n * `t_sessions` 的 ON CONFLICT 更新列值:只刷新 header 列,保留 head 游标\n * (`f_head_event_id`/`f_head_sequence`)与 materialization identity\n * (`f_incarnation`/`f_revision`)。方言无关,两后端共用(见\n * {@link sessionInsertRow})。\n */\nexport function sessionConflictRow(meta: SessionHeader): {\n fVersion: number;\n fCreatedAt: number;\n fCwd: string | null;\n fParentSession: string | null;\n fSeedLength: number | null;\n fOrigin: string | null;\n fDelegationDepth: number | null;\n} {\n return {\n fVersion: meta.version,\n fCreatedAt: meta.createdAt,\n fCwd: meta.cwd ?? null,\n fParentSession: meta.parentSession ?? null,\n fSeedLength: meta.seedLength ?? null,\n fOrigin: meta.origin ?? null,\n fDelegationDepth: meta.delegationDepth ?? null,\n };\n}\n\n/**\n * Remap a stored {@link SurfaceOp} from upstream seqs to persisted seqs. An\n * `append` op carries no seqs; a positional `replace`'s `start`/`end` name\n * surface nodes by UPSTREAM seq and must follow {@link SessionEvent.sourceEventSeqs}\n * through the same upstream→persisted map when delta filtering re-numbered the\n * log — otherwise the replacement range is looked up against DENSE seqs and the\n * surface fold rejects the log (\"start seq N not found in surface\").\n * @param op - the stored surface op.\n * @param remap - upstream→persisted seq mapping (identity when absent).\n * @returns the remapped surface op.\n */\nexport function remapSurfaceOp(op: SurfaceOp, remap: (seq: number) => number): SurfaceOp {\n if (op === \"append\") return op;\n return { op: \"replace\", start: remap(op.start), end: remap(op.end) };\n}\n\n/**\n * The compact metering events (`compaction/summary`, `compaction/prune`) carry the\n * token-meter's shadow-price claim in `data.shadowedRange`: the inclusive\n * surface-node seqs of the range the IMMEDIATELY following surface `replace`\n * shadows. The range names surface nodes by UPSTREAM seq, so it must follow\n * the replace's `surfaceOp` through the same upstream→persisted map — the\n * fold compares claim and replacement ranges for exact equality, and an\n * un-remapped claim (upstream) next to a remapped replacement range (dense)\n * makes replay fail loud (\"token surface: replace ... has no adjacent shadow\n * price\").\n * @param range - the stored shadowed range (upstream seqs).\n * @param remap - upstream→persisted seq mapping (identity when absent).\n * @returns the remapped shadowed range.\n */\nexport function remapShadowedRange(\n range: { start: number; end: number },\n remap: (seq: number) => number,\n): { start: number; end: number } {\n return { start: remap(range.start), end: remap(range.end) };\n}\n\n/**\n * Reconstruct a {@link SessionEvent} from a joined row. The emitted event\n * carries the DENSE persisted seq (`row.fSequence`); `sourceEventSeqs` entries,\n * a positional `replace` {@link SurfaceOp}'s range, and the compact metering\n * events' `shadowedRange` are resolved onto persisted seqs through `seqMap`\n * when the log was delta-filtered.\n *\n * Coordinate resolution is \"the stored f_sequence wins, when it is a valid\n * earlier reference\": after resume the upstream live log IS the dense log,\n * so events written since (checkpoint replaces, provenance) cite DENSE seqs.\n * A cited seq that exists in the dense space, precedes this event, and is\n * either a SURFACE node or has no upstream counterpart is kept as-is. A seq\n * outside the dense space (or a dense seq that is a non-surface event with an\n * upstream counterpart — an upstream-cited seq that happens to collide with a\n * later dense seq) is treated as an UPSTREAM reference and remapped through\n * the upstream→persisted map, again only when the result precedes this event.\n * Anything else is unresolvable and dropped (never kept verbatim — after\n * rewind re-uses the upstream space, keeping an upstream value would mix\n * coordinates and fail the Session seed validation).\n * @param row - the joined `t_session_events` + `t_events` row.\n * @param seqMap - upstream→persisted seq map (optional).\n * @param denseTypeMap - every persisted f_sequence → event type for the\n * session (optional; always passed together with `seqMap`).\n * @returns the reconstructed event; throws when a JSON column fails to parse\n * ({@link scanRows} treats that as a hole, not corruption, in the tail).\n */\nexport function rowToEvent(\n row: EventRow,\n seqMap?: ReadonlyMap<number, number>,\n denseTypeMap?: ReadonlyMap<number, string>,\n): SessionEvent {\n // Surface-metadata fields are conditional on the event type in the type\n // system; spread them so each variant gets only the fields it declares.\n // `sourceEventSeqs` must never mix coordinates: an unresolvable reference is\n // dropped, and the survivors are sorted/de-duplicated — see\n // {@link remapProvenance}. A fully pruned list is omitted entirely (the\n // event carries no provenance), matching the write path's null storage.\n const remap = (seq: number): number =>\n resolveProvenanceSeq(seq, row.fSequence, seqMap, denseTypeMap) ?? seq;\n const strictRemap = (seq: number): number | undefined =>\n resolveProvenanceSeq(seq, row.fSequence, seqMap, denseTypeMap);\n const surfaceFields = {\n ...(row.fSourceEventSeqs !== null\n ? (() => {\n const refs = remapProvenance(JSON.parse(row.fSourceEventSeqs) as number[], strictRemap);\n return refs.length > 0 ? { sourceEventSeqs: refs } : {};\n })()\n : {}),\n ...(row.fSurfaceOp !== null\n ? {\n surfaceOp: remapSurfaceOp(JSON.parse(row.fSurfaceOp) as SurfaceOp, remap),\n }\n : {}),\n };\n const data = JSON.parse(row.fData) as SessionEvent[\"data\"];\n // `compaction/summary` / `compaction/prune` are plugin-merged types whose\n // metering data is not part of the core `SessionEventMap`; narrow through a\n // structural view to remap the shadow-price claim's range (see\n // {@link remapShadowedRange}).\n if (row.fKind === \"compaction/summary\" || row.fKind === \"compaction/prune\") {\n const metering = data as unknown as { shadowedRange?: { start: number; end: number } };\n if (metering.shadowedRange !== undefined) {\n metering.shadowedRange = remapShadowedRange(metering.shadowedRange, remap);\n }\n }\n return {\n type: row.fKind as SessionEvent[\"type\"],\n seq: row.fSequence,\n time: row.fCreatedAt,\n data,\n ...surfaceFields,\n } as SessionEvent;\n}\n\n/**\n * Resolve one cited seq onto the DENSE persisted seq space.\n *\n * \"The stored f_sequence wins, when it is a valid earlier reference\": after\n * resume the upstream live log IS the dense log, so events written since\n * (checkpoint replaces, provenance) cite DENSE seqs. A cited seq that exists\n * in the dense space, precedes `eventSeq`, and is either a SURFACE node or has\n * no upstream counterpart is kept as-is. A seq outside the dense space (or a\n * dense seq that is a non-surface event with an upstream counterpart — an\n * upstream-cited seq that happens to collide with a later dense seq) is\n * treated as an UPSTREAM reference and remapped through the upstream→persisted\n * map, again only when the result precedes `eventSeq`. Anything else is\n * unresolvable (`undefined`): provenance entries are dropped rather than kept\n * verbatim, because after rewind re-uses the upstream space an upstream value\n * would mix coordinates and fail the Session seed validation.\n * @param seq - the cited seq (upstream or dense).\n * @param eventSeq - the citing event's dense seq.\n * @param seqMap - upstream→persisted seq map (optional).\n * @param denseTypeMap - every persisted f_sequence → event type (optional;\n * always passed together with `seqMap`).\n * @returns the dense seq to cite, or undefined when unresolvable.\n */\nexport function resolveProvenanceSeq(\n seq: number,\n eventSeq: number,\n seqMap?: ReadonlyMap<number, number>,\n denseTypeMap?: ReadonlyMap<number, string>,\n): number | undefined {\n if (seqMap === undefined && denseTypeMap === undefined) return seq;\n const kind = denseTypeMap?.get(seq);\n if (\n kind !== undefined &&\n seq < eventSeq &&\n (SURFACE_EVENT_TYPES.has(kind) || !seqMap?.has(seq))\n ) {\n return seq;\n }\n const dense = seqMap?.get(seq);\n if (dense !== undefined && dense < eventSeq) return dense;\n return undefined;\n}\n\n/**\n * Build the upstream→persisted seq map for one session's persisted events\n * (only meaningful when delta filtering re-numbered the log).\n *\n * A session re-opened by resume (or forked) persists the seed segment and the\n * new segment in ONE log: the seed rows keep the PARENT session's upstream seqs\n * while the resumed rows carry the child session's own upstream seqs, which\n * renumber from the seed boundary and therefore OVERLAP the parent space. The\n * FIRST mapping wins so a seed-segment event's provenance reference resolves to\n * the seed-space row it actually derived from (rows are ordered by persisted\n * seq, so the seed segment always precedes the resumed one); the resumed\n * segment's references are unique within their own space unless they point at a\n * shared value, which only the parent could have produced first.\n * @param rows - one session's seq rows (upstream + persisted), ordered by seq\n * ascending (only the two seq columns are needed).\n * @returns map from `f_original_seq` to `f_sequence` (first occurrence wins).\n */\nexport function buildSeqMap(\n rows: readonly Pick<EventRow, \"fSequence\" | \"fOriginalSeq\">[],\n): Map<number, number> {\n const map = new Map<number, number>();\n for (const row of rows) {\n if (!map.has(row.fOriginalSeq)) map.set(row.fOriginalSeq, row.fSequence);\n }\n return map;\n}\n\n/**\n * Normalize a stored provenance list into the persisted seq space.\n *\n * `sourceEventSeqs` references events by UPSTREAM seq; on read each entry is\n * remapped to the DENSE persisted seq through the upstream→persisted map. An\n * entry whose event never got a persisted row (a dropped delta) or no longer\n * has one cannot be remapped. Keeping such an entry verbatim would mix\n * upstream and dense coordinates in one list — after rewind re-uses the\n * upstream seq space, a stale reference can even collide with a live dense\n * seq — which the upstream Session seed validation rejects (duplicates /\n * non-monotonic / \"must reference earlier events\"). Unmappable entries are\n * dropped. Because the upstream space is reused, the remapped entries can\n * also arrive out of order or duplicated; the result is sorted and\n * de-duplicated so it is strictly increasing, as the upstream provenance\n * contract requires. The output's SET (the only thing surface-replace\n * shadowing checks) is unchanged by normalization.\n * @param refs - the event's `sourceEventSeqs` (upstream seqs).\n * @param remap - upstream→persisted seq mapping; undefined for an entry\n * whose event has no persisted row.\n * @returns the normalized, strictly increasing persisted seqs (possibly empty).\n */\nexport function remapProvenance(\n refs: readonly number[],\n remap: (seq: number) => number | undefined,\n): number[] {\n const mapped: number[] = [];\n for (const ref of refs) {\n const dense = remap(ref);\n if (dense !== undefined) mapped.push(dense);\n }\n mapped.sort((a, b) => a - b);\n const result: number[] = [];\n for (const seq of mapped) {\n if (result.length === 0 || result[result.length - 1] !== seq) result.push(seq);\n }\n return result;\n}\n\n/**\n * The event types that produce model-visible surface nodes (mirror of the\n * upstream `SURFACE_EVENT_TYPES`); only these can appear in a replace's\n * shadowed range, so only their seqs may be merged into provenance.\n */\nconst SURFACE_EVENT_TYPES = new Set([\"user/message\", \"assistant/message\", \"tool/result\"]);\n\n/**\n * Make a surface `replace` event's provenance cover its replacement range.\n *\n * The upstream Session seed validation requires a replace's `sourceEventSeqs`\n * to include every surface node it shadows (`assertProvenance`'s shadowed\n * check). Rewind re-uses the upstream seq space: a replace that survives a\n * truncation (e.g. a checkpoint `user/message` before the new boundary) has\n * its range remapped onto DENSE seqs, while its provenance references were\n * written in upstream seqs — references to rows rewind deleted are dropped on\n * read, so the surviving provenance no longer covers the range's dense nodes.\n * Merging every persisted SURFACE node seq inside the (dense) range into the\n * provenance makes the log loadable again; extra references are legal\n * provenance and never affect the fold (the fold consumes only the surfaceOp\n * range). Only events the map could resolve exist in the dense range, so the\n * merged list is strictly increasing after sort/dedup.\n * @param events - a session's dense event list (contiguous seqs), read in\n * full; mutated in place.\n */\nexport function normalizeSurfaceReplaceProvenance(events: SessionEvent[]): void {\n for (const event of events) {\n const raw = event as SessionEvent & { surfaceOp?: unknown; sourceEventSeqs?: number[] };\n const op = raw.surfaceOp;\n if (typeof op !== \"object\" || op === null || (op as { op?: string }).op !== \"replace\") {\n continue;\n }\n const { start, end } = op as { start: number; end: number };\n const refs = new Set(raw.sourceEventSeqs ?? []);\n for (const candidate of events) {\n if (\n candidate.seq >= start &&\n candidate.seq <= end &&\n SURFACE_EVENT_TYPES.has(candidate.type)\n ) {\n refs.add(candidate.seq);\n }\n }\n raw.sourceEventSeqs = [...refs].sort((a, b) => a - b);\n }\n}\n\n/**\n * Prune `sourceEventSeqs` references that cannot be remapped on read.\n *\n * `sourceEventSeqs` references events by UPSTREAM seq; on read the references\n * are remapped to the DENSE persisted seq through {@link buildSeqMap}. A\n * reference whose event never got a persisted row (a dropped delta, or a seq\n * that never existed) has no map entry and would replay as a `source >=\n * current seq` provenance violation, so the write path prunes it. A fully\n * pruned list is stored as null (no provenance) by the serializer.\n *\n * The `keep` predicate is the CALLER's view of resolvability: the write path\n * knows which upstream seqs THIS INSTANCE dropped (`WriteGuard.pruneRefs` —\n * per-instance knowledge, which must not prune references to rows another\n * instance persisted, e.g. a resume seed segment), while the one-shot repair\n * script knows which upstream seqs exist on DISK (full-database view). The\n * filter itself is shared so the rule lives in one place.\n * @param refs - the event's `sourceEventSeqs` (upstream seqs).\n * @param keep - true for a seq whose referenced event is resolvable.\n * @returns the pruned list.\n */\nexport function pruneSourceEventSeqs(\n refs: readonly number[],\n keep: (seq: number) => boolean,\n): number[] {\n return refs.filter(keep);\n}\n\n/**\n * Find the preserved prefix of ordered event rows. Fully written rows in an\n * interrupted final turn remain in the prefix. The first unparsable row or seq\n * gap after the last `turn/end` marks a tolerated torn tail; the same hole in\n * the committed region rejects.\n *\n * @param rows - one session's event rows, ordered by persisted seq ascending.\n * @param base - the persisted seq the first row is expected to carry; `0` for\n * a whole log, the requested `fromSeq` for a suffix read (`loadStoredFrom`).\n * @param seqMap - upstream→persisted seq map forwarded to {@link rowToEvent}.\n * @param denseTypeMap - every persisted f_sequence → type, forwarded to\n * {@link rowToEvent} (kept together with `seqMap`).\n * @returns the preserved event prefix, plus `tornFrom` — the persisted seq the\n * physical delete starts at — when a torn tail exists.\n */\nexport function scanRows(\n rows: readonly EventRow[],\n base = 0,\n seqMap?: ReadonlyMap<number, number>,\n denseTypeMap?: ReadonlyMap<number, string>,\n): { preserved: SessionEvent[]; tornFrom?: number } {\n // Pass 1: parse each row's data; a row whose data is not valid JSON is a hole.\n // (The seq/type COLUMNS are always present even when `data` is corrupt.)\n interface Parsed {\n ok: boolean;\n event?: SessionEvent;\n }\n const parsed: Parsed[] = rows.map((row) => {\n try {\n return { ok: true, event: rowToEvent(row, seqMap, denseTypeMap) };\n } catch {\n return { ok: false };\n }\n });\n\n // The last index that is a valid `turn/end` — holes through a closed turn\n // are always committed corruption.\n let lastTurnEnd = -1;\n for (let i = parsed.length - 1; i >= 0; i--) {\n if (parsed[i]?.ok && rows[i]?.fKind === \"turn/end\") {\n lastTurnEnd = i;\n break;\n }\n }\n\n // Preserve the contiguous prefix, including a complete interrupted turn;\n // holes through the last committed boundary throw, while later holes stop.\n const preserved: SessionEvent[] = [];\n for (let i = 0; i < rows.length; i++) {\n const p = parsed[i];\n if (!p?.ok || p.event === undefined) {\n if (i <= lastTurnEnd)\n throw new Error(\n `corrupt session log: unparsable committed event at seq ${rows[i]?.fSequence}`,\n );\n break; // torn tail fragment after the last turn/end — stop, tolerate\n }\n if (p.event.seq !== base + i) {\n if (i <= lastTurnEnd)\n throw new Error(\n `corrupt session log: seq gap in committed region (expected ${base + i}, got ${p.event.seq})`,\n );\n break; // gap after the last turn/end — torn tail, stop\n }\n preserved.push(p.event);\n }\n\n // Any rows past the preserved prefix are a never-committed torn tail; their\n // first seq is the deletion point for load's physical repair.\n return preserved.length < rows.length\n ? { preserved, tornFrom: base + preserved.length }\n : { preserved };\n}\n","/**\n * Per-instance write-authority state for one session-persistence backend.\n *\n * Each `PersistenceCoordinator` instance keeps its own UPSTREAM seq cursor in\n * memory, while this backend persists events under a DENSE seq. Two backend\n * instances (another `dsh` process, or a duplicate persistence plugin in the\n * same process) sharing one database therefore cannot both append to the same\n * session id: the second writer would silently re-number its batch onto the\n * first writer's tail and corrupt the log (event content and seq semantics all\n * misaligned — `UNIQUE(f_session_id, f_sequence)` cannot catch it because\n * dense re-numbering never collides). This guard records, per session, the\n * last CONFIRMED dense head this instance has seen — its own writes or\n * `loadStored` observations — and rejects any append whose on-disk head no\n * longer matches. One writer per session per log; a second writer fails loud\n * instead of corrupting the log. Different session ids are independent (each\n * has its own head), so two instances writing different sessions remain a\n * supported multi-process deployment.\n *\n * The guard also tracks the upstream seqs of events this instance has dropped\n * per session — delta events (`assistant/chunk`) and events the writer marked\n * `ignorable`. A later `assistant/message` may carry `sourceEventSeqs`\n * referencing those events' upstream seqs; the references can never be remapped\n * on read (the referenced events have no persisted row), so the write path\n * prunes them. The concurrent-writer guarantee above limits each session to\n * one writer, making this instance the only authority for its dropped seqs.\n *\n * Pure in-memory state machine — no I/O — so the coordinator's timing contract\n * (never-read vs. confirmed absence vs. confirmed head; confirm after append /\n * after load / re-confirm after repair) is directly unit-testable instead of\n * requiring end-to-end multi-instance setups.\n * @module @morlay/session-rdb/write-guard\n */\n\nimport type { SessionId } from \"@deepseek-ai/dsh-session\";\nimport { pruneSourceEventSeqs } from \"./log.ts\";\n\n/**\n * The write-authority state machine for one backend instance. Not part of the\n * {@link Backend} seam: it guards the orchestration layer's own invariants and\n * lives entirely in memory.\n */\nexport class WriteGuard {\n /**\n * Last CONFIRMED dense head per session — the head this instance itself\n * wrote or observed via `loadStored`. `-1` records a confirmed absence (no\n * row). `undefined` (absent from the map) means this instance never read or\n * wrote the session.\n */\n private readonly headSeqs = new Map<SessionId, number>();\n\n /**\n * Upstream seqs of delta events dropped per session. Mirrors `headSeqs` in\n * shape: the concurrent-writer guarantee limits each session to one writer,\n * so this instance is the only authority for its dropped seqs.\n */\n private readonly filteredSeqs = new Map<SessionId, Set<number>>();\n\n /**\n * Record a head this instance actually observed or wrote.\n * @param id - the session id.\n * @param head - the confirmed dense head, or `-1` for a confirmed absence\n * (a fresh session this instance has read about — a later append to a\n * session that meanwhile got a row must reject).\n */\n confirmHead(id: SessionId, head: number): void {\n this.headSeqs.set(id, head);\n }\n\n /**\n * Fail loud when the on-disk head no longer matches this instance's last\n * confirmed head for the session. `undefined` (never read/written here) is\n * only acceptable for a session with NO row: a row written by someone else\n * means this instance's coordinator cursor is not the log's authority.\n * @param id - the session id.\n * @param storedHead - the on-disk head cursor, read inside the append\n * transaction before any re-numbering happens.\n */\n assertNoConcurrentWriter(id: SessionId, storedHead: number): void {\n const known = this.headSeqs.get(id);\n if (known === undefined) {\n if (storedHead !== -1) {\n throw new Error(\n `session \"${id}\" has a persisted log this instance has not read; another writer may own it — load the session first`,\n );\n }\n return;\n }\n if (known !== storedHead) {\n throw new Error(\n `session \"${id}\" was modified by another writer (stored head ${storedHead}, this instance last confirmed head ${known}); ` +\n \"concurrent writers on one session are not supported\",\n );\n }\n }\n\n /**\n * Record the upstream seqs of events dropped for a session (delta events and\n * ignorable events), so a later batch's `assistant/message` can prune\n * `sourceEventSeqs` references to events that never got a persisted row.\n * @param id - the session id.\n * @param seqs - the dropped events' upstream seqs (pure-delta batches included).\n */\n noteDropped(id: SessionId, seqs: Iterable<number>): void {\n const known = this.filteredSeqs.get(id) ?? new Set<number>();\n for (const seq of seqs) known.add(seq);\n this.filteredSeqs.set(id, known);\n }\n\n /**\n * Prune `sourceEventSeqs` references that hit this session's dropped-delta\n * seq set. `undefined`-like state (no dropped seqs recorded for the session)\n * leaves the list untouched, matching the write path's \"no known drops →\n * keep verbatim\" semantics (repair closers, which never carry provenance,\n * call through the identity path).\n *\n * The predicate is THIS INSTANCE's view (see\n * {@link pruneSourceEventSeqs}): only seqs it knows were dropped are\n * pruned — references to rows persisted by another instance (e.g. a resume\n * seed segment) must survive, so the disk-wide view used by the one-shot\n * repair script is not applicable here.\n * @param id - the session id.\n * @param refs - the event's `sourceEventSeqs` (upstream seqs).\n * @returns the pruned list; identical content when nothing was dropped.\n */\n pruneRefs(id: SessionId, refs: readonly number[]): number[] {\n const dropped = this.filteredSeqs.get(id);\n if (dropped === undefined || dropped.size === 0) return [...refs];\n return pruneSourceEventSeqs(refs, (seq) => !dropped.has(seq));\n }\n}\n","/**\n * 方言无关的表描述类型:`src/entities/*.ts` 以本模块的类型声明每张表一次,\n * SQLite 与 PostgreSQL 的表对象(drizzle `sqliteTable` / `pgTable`)和建表\n * DDL 都由这些描述生成——实体定义是唯一来源,不再手写两套 drizzle 表与裸\n * SQL DDL。\n *\n * @module @morlay/session-rdb/entities/types\n */\n\n/**\n * 列类型(方言无关):\n * - `serial` — 自增主键(sqlite `INTEGER PRIMARY KEY AUTOINCREMENT` /\n * pg `SERIAL PRIMARY KEY`);\n * - `integer` — 32 位整数(sqlite `INTEGER` / pg `INTEGER`);\n * - `bigint` — 64 位整数(sqlite `INTEGER` / pg `BIGINT`,用于毫秒时间戳);\n * - `text` — 文本(sqlite `TEXT` / pg `TEXT`)。\n */\nexport type ColumnTypeName = \"serial\" | \"integer\" | \"bigint\" | \"text\";\n\n/** 外键级联动作(drizzle `UpdateDeleteAction` 的子集)。 */\nexport type DeleteAction = \"cascade\" | \"set null\" | \"restrict\" | \"no action\";\n\n/** 一列的定义(物理列名一律 `f_` 前缀)。 */\nexport interface ColumnDef {\n /** 物理列名(`f_` 前缀)。 */\n name: string;\n type: ColumnTypeName;\n notNull?: boolean;\n /** 列级主键;`serial` 列同时携带自增语义。 */\n primaryKey?: boolean;\n /** 字面量默认值(字符串/数字)。 */\n default?: string | number;\n /** 列级 UNIQUE。 */\n unique?: boolean;\n /** 列级外键(目标表 + 目标列 + 级联动作)。 */\n references?: {\n table: string;\n column: string;\n onDelete?: DeleteAction;\n };\n}\n\n/** 表级 CHECK 约束(expression 为 SQL 片段,如 `f_singleton = 1`)。 */\nexport interface CheckDef {\n name: string;\n expression: string;\n}\n\n/** 表级 UNIQUE 约束。 */\nexport interface UniqueDef {\n name?: string;\n columns: string[];\n}\n\n/** 普通索引(建表后独立 CREATE INDEX)。 */\nexport interface IndexDef {\n name: string;\n columns: string[];\n}\n\n/** 一张表的完整描述。 */\nexport interface TableDef {\n /** 物理表名(`t_` 前缀)。 */\n name: string;\n columns: ColumnDef[];\n checks?: CheckDef[];\n uniques?: UniqueDef[];\n indexes?: IndexDef[];\n}\n\n/** 把物理列名转成 drizzle 表对象的属性名(`f_session_id` → `fSessionId`)。 */\nexport function toProperty(name: string): string {\n return name.replace(/_([a-z])/g, (_match, char: string) => char.toUpperCase());\n}\n","/**\n * 从方言无关的表描述生成 SQLite 的 drizzle `sqliteTable` 对象。查询的类型\n * 安全由调用方的手写行接口(`backend.ts` 的 `SessionRow` / `EventRow` /\n * `EventInsert`)与显式投影兜底——drizzle 无法从运行时构建的列映射保留\n * 精确的列类型。\n *\n * @module @morlay/session-rdb/entities/to-sqlite\n */\n\nimport { sql } from \"drizzle-orm\";\nimport {\n check as sqliteCheck,\n index,\n integer,\n sqliteTable,\n text,\n unique,\n type AnySQLiteColumn,\n type AnySQLiteTable,\n type SQLiteColumnBuilder,\n} from \"drizzle-orm/sqlite-core\";\nimport { toProperty, type ColumnDef, type TableDef } from \"../entities/types.ts\";\n\n/** 构建过程中共享的目标表注册表(供列级 `references` 闭包延迟解析)。 */\ntype TableRegistry = Record<string, AnySQLiteTable>;\n\nfunction buildColumn(c: ColumnDef, tables: TableRegistry): SQLiteColumnBuilder {\n // 具体 builder 类型(SQLiteIntegerBuilder / SQLiteTextBuilder)与基类\n // `SQLiteColumnBuilder` 在方法层面不兼容(泛型逆变),构建阶段用宽松类型\n // 合并,返回时收窄为基类;查询的类型安全由手写行接口兜底。\n let col: any;\n switch (c.type) {\n case \"text\":\n col = text(c.name);\n break;\n case \"serial\":\n col = integer(c.name).primaryKey({ autoIncrement: true });\n break;\n case \"integer\":\n case \"bigint\": {\n const built = integer(c.name);\n col = c.primaryKey ? built.primaryKey() : built;\n break;\n }\n }\n if (c.notNull) col = col.notNull();\n if (c.default !== undefined) col = col.default(c.default);\n if (c.unique) col = col.unique();\n if (c.references) {\n const { table, column, onDelete } = c.references;\n col = col.references(\n () => (tables[table] as unknown as Record<string, AnySQLiteColumn>)[toProperty(column)],\n { onDelete },\n );\n }\n return col as SQLiteColumnBuilder;\n}\n\n/** 由表描述构建 SQLite drizzle 表对象(按传入顺序;外键目标须先构建)。 */\nexport function toSqliteSchema(defs: readonly TableDef[]): Record<string, AnySQLiteTable> {\n const tables: TableRegistry = {};\n for (const def of defs) {\n const columns: Record<string, SQLiteColumnBuilder> = {};\n for (const c of def.columns) columns[toProperty(c.name)] = buildColumn(c, tables);\n const extra = (self: Record<string, unknown>) => [\n ...(def.checks ?? []).map((c) => sqliteCheck(c.name, sql.raw(c.expression))),\n ...(def.uniques ?? []).map((u) =>\n unique(u.name).on(\n ...(u.columns.map((name) => self[toProperty(name)] as AnySQLiteColumn) as [\n AnySQLiteColumn,\n ...AnySQLiteColumn[],\n ]),\n ),\n ),\n ...(def.indexes ?? []).map((i) =>\n index(i.name).on(\n ...(i.columns.map((name) => self[toProperty(name)] as AnySQLiteColumn) as [\n AnySQLiteColumn,\n ...AnySQLiteColumn[],\n ]),\n ),\n ),\n ];\n tables[def.name] = sqliteTable(\n def.name,\n columns as Record<string, SQLiteColumnBuilder>,\n extra as never,\n );\n }\n return tables;\n}\n","/**\n * 从方言无关的表描述生成 PostgreSQL 的 drizzle `pgTable` 对象。查询的类型\n * 安全由调用方的手写行接口与显式投影兜底(同 `to-sqlite.ts`)。\n *\n * @module @morlay/session-rdb/entities/to-postgres\n */\n\nimport { sql } from \"drizzle-orm\";\nimport {\n bigint,\n check as pgCheck,\n index,\n integer,\n pgTable,\n serial,\n text,\n unique,\n type AnyPgColumn,\n type AnyPgColumnBuilder,\n type AnyPgTable,\n} from \"drizzle-orm/pg-core\";\nimport { toProperty, type ColumnDef, type TableDef } from \"../entities/types.ts\";\n\n/** 构建过程中共享的目标表注册表(供列级 `references` 闭包延迟解析)。 */\ntype TableRegistry = Record<string, AnyPgTable>;\n\nfunction buildColumn(c: ColumnDef, tables: TableRegistry): AnyPgColumnBuilder {\n // 具体 builder 类型与 AnyPgColumnBuilder 在方法层面不兼容,构建阶段用宽松类型。\n let col: any;\n switch (c.type) {\n case \"text\":\n col = text(c.name);\n break;\n case \"serial\":\n col = serial(c.name).primaryKey();\n break;\n case \"integer\": {\n const built = integer(c.name);\n col = c.primaryKey ? built.primaryKey() : built;\n break;\n }\n case \"bigint\": {\n const built = bigint(c.name, { mode: \"number\" });\n col = c.primaryKey ? built.primaryKey() : built;\n break;\n }\n }\n if (c.notNull) col = col.notNull();\n if (c.default !== undefined) col = col.default(c.default);\n if (c.unique) col = col.unique();\n if (c.references) {\n const { table, column, onDelete } = c.references;\n col = col.references(\n () => (tables[table] as unknown as Record<string, AnyPgColumn>)[toProperty(column)],\n { onDelete },\n );\n }\n return col;\n}\n\n/** 由表描述构建 PostgreSQL drizzle 表对象(按传入顺序;外键目标须先构建)。 */\nexport function toPostgresSchema(defs: readonly TableDef[]): Record<string, AnyPgTable> {\n const tables: TableRegistry = {};\n for (const def of defs) {\n const columns: Record<string, AnyPgColumnBuilder> = {};\n for (const c of def.columns) columns[toProperty(c.name)] = buildColumn(c, tables);\n const extra = (self: Record<string, unknown>) => [\n ...(def.checks ?? []).map((c) => pgCheck(c.name, sql.raw(c.expression))),\n ...(def.uniques ?? []).map((u) =>\n unique(u.name).on(\n ...(u.columns.map((name) => self[toProperty(name)] as AnyPgColumn) as [\n AnyPgColumn,\n ...AnyPgColumn[],\n ]),\n ),\n ),\n ...(def.indexes ?? []).map((i) =>\n index(i.name).on(\n ...(i.columns.map((name) => self[toProperty(name)] as AnyPgColumn) as [\n AnyPgColumn,\n ...AnyPgColumn[],\n ]),\n ),\n ),\n ];\n tables[def.name] = pgTable(\n def.name,\n columns as Record<string, AnyPgColumnBuilder>,\n extra as never,\n );\n }\n return tables;\n}\n","/**\n * 从方言无关的表描述生成建表 DDL(`CREATE TABLE IF NOT EXISTS` 与独立索引)。\n * SQLite 表附带 `STRICT`,PostgreSQL 用 `SERIAL` / `BIGINT` 等方言类型。\n * 这是实体的唯一 DDL 来源——不手写 SQL 字符串。\n *\n * @module @morlay/session-rdb/entities/ddl\n */\n\nimport type { ColumnDef, TableDef } from \"../entities/types.ts\";\n\nexport type Dialect = \"sqlite\" | \"postgres\";\n\nfunction sqlType(dialect: Dialect, type: ColumnDef[\"type\"]): string {\n switch (type) {\n case \"serial\":\n return dialect === \"sqlite\" ? \"INTEGER\" : \"SERIAL\";\n case \"integer\":\n return \"INTEGER\";\n case \"bigint\":\n return dialect === \"sqlite\" ? \"INTEGER\" : \"BIGINT\";\n case \"text\":\n return \"TEXT\";\n }\n}\n\nfunction literal(value: string | number): string {\n return typeof value === \"string\" ? `'${value.replace(/'/g, \"''\")}'` : String(value);\n}\n\nfunction quote(name: string): string {\n return `\"${name}\"`;\n}\n\nfunction columnSql(dialect: Dialect, c: ColumnDef): string {\n let sql = `${quote(c.name)} ${sqlType(dialect, c.type)}`;\n if (c.primaryKey) sql += \" PRIMARY KEY\";\n if (c.type === \"serial\" && dialect === \"sqlite\") sql += \" AUTOINCREMENT\";\n if (c.notNull) sql += \" NOT NULL\";\n if (c.default !== undefined) sql += ` DEFAULT ${literal(c.default)}`;\n if (c.unique) sql += \" UNIQUE\";\n if (c.references) {\n sql += ` REFERENCES ${quote(c.references.table)}(${quote(c.references.column)})`;\n if (c.references.onDelete) sql += ` ON DELETE ${c.references.onDelete.toUpperCase()}`;\n }\n return sql;\n}\n\n/** 一张表的 `CREATE TABLE IF NOT EXISTS`(表级约束内联在列清单末尾)。 */\nexport function createTableSql(dialect: Dialect, def: TableDef): string {\n const parts = def.columns.map((c) => columnSql(dialect, c));\n for (const ck of def.checks ?? []) parts.push(`CHECK (${ck.expression})`);\n for (const u of def.uniques ?? []) {\n parts.push(`UNIQUE (${u.columns.map(quote).join(\", \")})`);\n }\n const strict = dialect === \"sqlite\" ? \" STRICT\" : \"\";\n return `CREATE TABLE IF NOT EXISTS ${quote(def.name)} (\\n ${parts.join(\",\\n \")}\\n)${strict}`;\n}\n\n/** 一张表的独立索引语句(两方言索引 DDL 相同,无需 dialect 参数)。 */\nexport function createIndexSql(def: TableDef, name: string): string {\n const idx = def.indexes?.find((i) => i.name === name);\n if (idx === undefined) throw new Error(`unknown index \"${name}\" on table \"${def.name}\"`);\n return `CREATE INDEX IF NOT EXISTS ${quote(idx.name)} ON ${quote(def.name)}(${idx.columns\n .map(quote)\n .join(\", \")})`;\n}\n\n/** 一组表的全部建表语句(每表一条 CREATE TABLE + 每条索引)。 */\nexport function createTablesSql(dialect: Dialect, defs: readonly TableDef[]): string[] {\n const statements: string[] = [];\n for (const def of defs) {\n statements.push(createTableSql(dialect, def));\n for (const idx of def.indexes ?? []) statements.push(createIndexSql(def, idx.name));\n }\n return statements;\n}\n","import type { TableDef } from \"./types.ts\";\n\n/**\n * `t_persistence_state` — 单例 store 身份行。`f_singleton` 上的 CHECK 把表\n * 钉死为一行(`f_singleton = 1`)。\n */\nexport const persistenceState: TableDef = {\n name: \"t_persistence_state\",\n columns: [\n { name: \"f_singleton\", type: \"integer\", primaryKey: true },\n { name: \"f_store_id\", type: \"text\", notNull: true },\n ],\n checks: [{ name: \"ck_persistence_state_singleton\", expression: \"f_singleton = 1\" }],\n};\n","import type { TableDef } from \"./types.ts\";\n\n/**\n * `t_schema_meta` — PostgreSQL 的 schema 版本 / 应用身份载体(SQLite 用\n * `PRAGMA user_version` / `application_id`,PG 无等价 pragma)。键值表:\n * `schema_version` 与 `application_id` 两行在首次初始化时写入。\n * 仅 PostgreSQL 后端使用。\n */\nexport const schemaMeta: TableDef = {\n name: \"t_schema_meta\",\n columns: [\n { name: \"f_key\", type: \"text\", primaryKey: true },\n { name: \"f_value\", type: \"text\", notNull: true },\n ],\n};\n","import type { TableDef } from \"./types.ts\";\n\n/**\n * `t_sessions` — 会话元数据(`SessionHeader` 列)+ playpen 风格 head 游标\n * (`f_head_event_id` / `f_head_sequence`,事务内维护,append 时提供 parent\n * 链与下一个 seq)。行的存在即 materialized 信号。\n */\nexport const sessions: TableDef = {\n name: \"t_sessions\",\n columns: [\n { name: \"f_id\", type: \"serial\", primaryKey: true },\n { name: \"f_session_id\", type: \"text\", notNull: true, unique: true },\n { name: \"f_head_event_id\", type: \"text\", notNull: true, default: \"\" },\n { name: \"f_head_sequence\", type: \"integer\", notNull: true, default: -1 },\n { name: \"f_version\", type: \"integer\", notNull: true },\n { name: \"f_created_at\", type: \"bigint\", notNull: true },\n { name: \"f_cwd\", type: \"text\" },\n { name: \"f_parent_session\", type: \"text\" },\n { name: \"f_seed_length\", type: \"integer\" },\n { name: \"f_origin\", type: \"text\" },\n { name: \"f_delegation_depth\", type: \"integer\" },\n { name: \"f_incarnation\", type: \"text\", notNull: true },\n { name: \"f_revision\", type: \"integer\", notNull: true },\n ],\n};\n","import type { TableDef } from \"./types.ts\";\n\n/**\n * `t_events` — 全局可寻址的持久化事件实体:`f_event_id`(UUID 唯一)、\n * `f_parent_id`(事件链,空串为 root)、`f_kind`(= 上游 `type`)、\n * `f_role` / `f_name` / `f_action_id`(playpen 事件维度)、`f_encoding`\n * (`json`)、`f_data`(JSON 文本)、`f_created_at`(= `time`)、\n * `f_original_seq`(上游 seq)以及 surface 元数据列(JSON 文本或 NULL)。\n */\nexport const events: TableDef = {\n name: \"t_events\",\n columns: [\n { name: \"f_id\", type: \"serial\", primaryKey: true },\n { name: \"f_event_id\", type: \"text\", notNull: true, unique: true },\n { name: \"f_parent_id\", type: \"text\", notNull: true, default: \"\" },\n { name: \"f_kind\", type: \"text\", notNull: true, default: \"\" },\n { name: \"f_role\", type: \"text\", notNull: true, default: \"\" },\n { name: \"f_name\", type: \"text\", notNull: true, default: \"\" },\n { name: \"f_action_id\", type: \"text\", notNull: true, default: \"\" },\n { name: \"f_encoding\", type: \"text\", notNull: true, default: \"\" },\n { name: \"f_data\", type: \"text\", notNull: true },\n { name: \"f_created_at\", type: \"bigint\", notNull: true, default: 0 },\n { name: \"f_original_seq\", type: \"integer\", notNull: true },\n { name: \"f_source_event_seqs\", type: \"text\" },\n { name: \"f_surface_op\", type: \"text\" },\n ],\n // 无独立索引:查询只经 `f_event_id`(列级 UNIQUE 自动建唯一索引,join 查找侧)\n // 与 `t_session_events` 的复合索引(按 session 过滤后回表取本表列)。事件链\n // `f_parent_id` 仅在写路径构造(读时不回读该列),无按 kind/role/created_at\n // 的查询——不再为不可达查询维护索引(写放大)。\n};\n","import type { TableDef } from \"./types.ts\";\n\n/**\n * `t_session_events` — 会话↔事件桥接表。`(f_session_id, f_sequence)` 唯一且\n * 有序,会话 log 按稠密 seq 读取;删除 torn tail 只删桥接行(事件实体作为\n * 全局行保留)。\n */\nexport const sessionEvents: TableDef = {\n name: \"t_session_events\",\n columns: [\n { name: \"f_id\", type: \"serial\", primaryKey: true },\n {\n name: \"f_session_id\",\n type: \"text\",\n notNull: true,\n references: { table: \"t_sessions\", column: \"f_session_id\", onDelete: \"cascade\" },\n },\n {\n name: \"f_event_id\",\n type: \"text\",\n notNull: true,\n references: { table: \"t_events\", column: \"f_event_id\", onDelete: \"cascade\" },\n },\n { name: \"f_sequence\", type: \"integer\", notNull: true },\n ],\n uniques: [\n { name: \"uq_session_events_session_sequence\", columns: [\"f_session_id\", \"f_sequence\"] },\n ],\n // 不另建普通索引:`UNIQUE(f_session_id, f_sequence)` 约束自动创建的唯一索引\n // 已覆盖本表的全部访问模式(按 session 过滤 + 按 seq 范围/排序/取尾)。\n};\n","/**\n * 实体纯定义:每张表一个文件,方言无关的表描述(列 / 约束 / 索引 / 外键)。\n * 具体后端的 drizzle 表对象与建表 DDL 由 `src/adapters/` 从这里转化生成。\n *\n * @module @morlay/session-rdb/entities\n */\n\nimport { persistenceState } from \"./persistence-state.ts\";\nimport { schemaMeta } from \"./schema-meta.ts\";\nimport { sessions } from \"./sessions.ts\";\nimport { events } from \"./events.ts\";\nimport { sessionEvents } from \"./session-events.ts\";\n\nexport { persistenceState };\nexport { schemaMeta };\nexport { sessions };\nexport { events };\nexport { sessionEvents };\nexport type {\n ColumnDef,\n TableDef,\n CheckDef,\n UniqueDef,\n IndexDef,\n ColumnTypeName,\n DeleteAction,\n} from \"./types.ts\";\n\n/** SQLite 后端使用的表(不含 pg 专用的 `t_schema_meta`)。 */\nexport const sqliteTableDefs = [persistenceState, sessions, events, sessionEvents] as const;\n\n/** PostgreSQL 后端使用的表。 */\nexport const postgresTableDefs = [\n persistenceState,\n schemaMeta,\n sessions,\n events,\n sessionEvents,\n] as const;\n","/**\n * Schema + event classification for the RDB session-persistence backend.\n *\n * The table layout follows the playpen-session store (three-table split):\n * `t_sessions` holds the header plus the head cursor, `t_events` holds each\n * event as a GLOBALLY addressable entity (event id + parent chain +\n * kind/role/name/action-id dimensions), and `t_session_events` bridges\n * sessions to events in per-session seq order.\n *\n * Naming is uniform: every table carries the `t_` prefix and every column the\n * `f_` prefix. The entities are declared ONCE in `src/entities/` (dialect-free\n * table descriptions); this module derives the SQLite drizzle tables from\n * them (STRICT + version/identity pragmas live with the SQLite backend in\n * `sqlite.ts`). There is no migration toolchain and no hand-written DDL.\n *\n * Delta content is NOT persisted: `assistant/chunk` events are dropped at\n * write time, and surviving events are re-numbered to a dense persisted seq\n * (`f_original_seq` keeps the upstream seq, so `sourceEventSeqs` provenance\n * can be remapped on read — see `log.ts`). This gives the backend the same\n * \"ephemeral chunks stay out of the canonical log\" semantics the persistence\n * proposal records, without requiring the upstream session layer to skip seqs.\n *\n * @module @morlay/session-rdb/schema\n */\n\nimport type { SessionEvent } from \"@deepseek-ai/dsh-session\";\nimport { toSqliteSchema } from \"./adapters/index.ts\";\nimport { sqliteTableDefs } from \"./entities/index.ts\";\n\n/**\n * The on-disk schema version. Bumped only on a breaking change to the table\n * layout; orthogonal to a session's own `version` (which versions the EVENT\n * vocabulary, stored per session in the `t_sessions` row).\n */\nexport const SCHEMA_VERSION = 1;\n\n/** SQLite application id protecting unrelated databases from persistence writes. */\nexport const SESSION_PERSISTENCE_SQLITE_APPLICATION_ID = 0x44534850;\n\n/**\n * Event types whose CONTENT is not persisted: the backend drops these rows\n * entirely and re-numbers the surviving events to a dense persisted seq.\n * Mirrors the persistence proposal's \"ephemeral events never enter the\n * canonical log\" split.\n */\nexport const EPHEMERAL_EVENT_TYPES = [\"assistant/chunk\"] as const;\n\n/** `t_events.f_encoding` value: JSON text. Future compression would switch this per row. */\nexport const EVENT_ENCODING = \"json\";\n\n/**\n * SQLite drizzle tables derived from the single entity definitions in\n * `src/entities/`. The runtime column objects are authoritative; TS type\n * safety of queries is carried by the hand-written row interfaces in\n * `backend.ts` (drizzle cannot infer precise column types from a runtime-built\n * column map, so the table handles are deliberately loose).\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nconst sqliteTables: Record<string, any> = toSqliteSchema(sqliteTableDefs);\n\n/** `t_persistence_state` — the singleton row holding the store identity. */\nexport const tPersistenceState = sqliteTables[\"t_persistence_state\"]!;\n\n/** `t_sessions` — the out-of-log metadata plus the playpen-style head cursor. */\nexport const tSessions = sqliteTables[\"t_sessions\"]!;\n\n/** `t_events` — the globally addressable persisted event entity. */\nexport const tEvents = sqliteTables[\"t_events\"]!;\n\n/** `t_session_events` — the session↔event bridge. */\nexport const tSessionEvents = sqliteTables[\"t_session_events\"]!;\n\n/**\n * A row of the `t_sessions` table — the out-of-log metadata ({@link SessionHeader})\n * plus the playpen-style head cursor (the last committed event id and seq). The\n * row's EXISTENCE is the materialization signal: it is written only by the\n * first non-empty append (lazy materialization), so a created-but-never-appended\n * session has no row and is absent from `list`.\n *\n * The canonical shared shape lives in `backend.ts` (dialect-neutral); the\n * drizzle-derived select model is structurally compatible.\n */\nexport type { SessionRow } from \"./backend.ts\";\n\n/** @see {@link import(\"./backend.ts\").EventRow} — shared with the PostgreSQL backend. */\nexport type { EventRow } from \"./backend.ts\";\n\n/**\n * Journal modes the backend will run under. `wal` is the default and the\n * durability model the persistence ADR records; the rollback-journal modes\n * (`delete`/`truncate`/`persist`) exist for filesystems where WAL's\n * shared-memory files do not work (network mounts). `memory`/`off` are\n * excluded: dropping journal durability silently contradicts what this\n * backend promises.\n */\nexport type JournalMode = \"wal\" | \"delete\" | \"truncate\" | \"persist\";\n\n/**\n * How long a connection waits for a contended write lock before failing with\n * `SQLITE_BUSY`. SQLite's default is 0 (fail immediately): with two processes\n * sharing one database (a second `dsh` instance on the same `sessions.sqlite`),\n * every append that meets an in-flight commit would fail and that process's\n * session would silently lose its tail. A nonzero wait makes the contention\n * window a queue instead of a loss.\n */\nexport const DEFAULT_BUSY_TIMEOUT_MS = 5000;\n\n/**\n * Whether an event type is ephemeral (its content must not be persisted).\n * @param type - the upstream `SessionEvent.type`.\n * @returns true for delta events the backend drops at write time.\n */\nexport function isEphemeralType(type: string): boolean {\n return (EPHEMERAL_EVENT_TYPES as readonly string[]).includes(type);\n}\n\n/**\n * Whether an event must be persisted. An event is dropped at write time when\n * its type is ephemeral (content not persisted) OR the writer marked it\n * `ignorable` — the envelope contract promises loss of an ignorable event\n * cannot affect reconstruction, so it never enters the canonical log (the\n * upstream seq is still recorded for provenance pruning, exactly like a\n * dropped delta). `session-branch/version` is a branch-layer lineage fact\n * carried as an ignorable seed event: it stays in the LIVE log but is NOT\n * persisted here — the branch provider persists it in its own version table\n * (`t_branch_versions`), keeping canonical-log semantics intact.\n */\nexport function isPersistedEvent(event: SessionEvent): boolean {\n return !isEphemeralType(event.type) && event.ignorable !== true;\n}\n\n/**\n * Map a persisted event onto the playpen event dimensions. `f_kind` is the\n * upstream type; `f_role`/`f_name`/`f_action_id` are the playpen classification\n * columns. Unknown (plugin-merged) event types keep the playpen defaults so a\n * future extension can classify them without a schema change.\n * @param event - the event to classify (never an ephemeral type at write time).\n * @returns the role, name, and action-id column values.\n */\nexport function eventDimensions(event: SessionEvent): {\n role: string;\n name: string;\n actionId: string;\n} {\n switch (event.type) {\n case \"turn/start\":\n case \"turn/end\":\n case \"step/start\":\n case \"step/end\":\n case \"session/end-seed\":\n return { role: \"turn\", name: \"\", actionId: \"\" };\n case \"user/message\":\n case \"request/header\":\n case \"request/context\":\n return { role: \"user\", name: \"\", actionId: \"\" };\n case \"assistant/message\":\n return { role: \"model\", name: \"\", actionId: \"\" };\n case \"tool/call\":\n return { role: \"function\", name: event.data.name, actionId: event.data.callId };\n case \"tool/result\": {\n // Optional chain: the coordinator migrates pre-identity legacy events\n // only on READ; an append may still carry the old shape without `message`.\n const block = event.data.message?.content[0];\n return { role: \"function\", name: \"\", actionId: block?.toolCallId ?? \"\" };\n }\n case \"todo/write\":\n return { role: \"state\", name: \"todos\", actionId: \"\" };\n default:\n return { role: \"\", name: \"\", actionId: \"\" };\n }\n}\n","/**\n * SQLite 存储后端:在 `node:sqlite` `DatabaseSync` 之上实现 {@link Backend}。\n * drizzle 经 `drizzle-orm/node-sqlite` 驱动包装,查询语义与 PostgreSQL 后端\n * 共用同一套 {@link BackendTx} 原语。数据库打开/建表/schema 版本与身份校验\n * ({@link openDatabase})也归本模块所有——`SqliteBackend` 的实现不跨文件。\n *\n * 事务:SQLite 单连接,`BEGIN IMMEDIATE` 提前获取写锁(受 `busy_timeout`\n * pragma 排队保护);`COMMIT`/`ROLLBACK` 之后同一连接继续服务普通查询。\n * @module @morlay/session-rdb/sqlite\n */\n\nimport { randomUUID } from \"node:crypto\";\nimport { statSync } from \"node:fs\";\nimport { mkdir, open } from \"node:fs/promises\";\nimport { dirname, resolve } from \"node:path\";\nimport { DatabaseSync } from \"node:sqlite\";\nimport { and, desc, eq, gte, inArray, sql } from \"drizzle-orm\";\nimport { drizzle, type NodeSQLiteDatabase } from \"drizzle-orm/node-sqlite\";\nimport type { SessionHeader, SessionId } from \"@deepseek-ai/dsh-session\";\nimport {\n type Backend,\n type BackendTx,\n type EventInsert,\n type EventRow,\n type SessionRow,\n} from \"./backend.ts\";\nimport { createTablesSql } from \"./adapters/index.ts\";\nimport { sqliteTableDefs } from \"./entities/index.ts\";\nimport { sessionConflictRow, sessionInsertRow } from \"./log.ts\";\nimport {\n DEFAULT_BUSY_TIMEOUT_MS,\n SCHEMA_VERSION,\n SESSION_PERSISTENCE_SQLITE_APPLICATION_ID,\n tEvents,\n tPersistenceState,\n tSessionEvents,\n tSessions,\n type JournalMode,\n} from \"./schema.ts\";\n\n/** The drizzle handle over the open `node:sqlite` database, plus its raw client. */\ntype SqliteDb = NodeSQLiteDatabase & { $client: DatabaseSync };\n\n/**\n * Process-wide SQLite write-transaction queues, keyed by database path.\n * SQLite allows exactly one writer, and the async transaction callback (see\n * {@link SqliteBackend.transaction}) must never overlap another connection's\n * `BEGIN IMMEDIATE` inside this process: the second, synchronous BEGIN would\n * busy-wait on the lock and freeze the event loop, so the lock holder could\n * never commit (deadlock until busy_timeout). Serializing per PATH removes the\n * gap entirely.\n *\n * The queue is per path, not global: two backends on DIFFERENT files have no\n * lock to contend for, so serializing them would be pure waste. Two instances\n * sharing one file (the supported multi-process deployment) still share one\n * queue, preserving the deadlock guarantee. `:memory:` databases are distinct\n * per connection but share the key — serializing them is harmless (tests).\n */\nconst sqliteTxQueues = new Map<string, Promise<void>>();\n\n/** Run `fn` behind the write-transaction queue for one database path. */\nfunction enqueueSqliteTx<T>(path: string, fn: () => Promise<T>): Promise<T> {\n const tail = sqliteTxQueues.get(path) ?? Promise.resolve();\n const run = tail.then(fn);\n // A failed transaction must not poison the queue for later ones.\n sqliteTxQueues.set(\n path,\n run.then(\n () => undefined,\n () => undefined,\n ),\n );\n return run;\n}\n\n/**\n * Exclusively create a missing database file with owner-only permissions.\n * Existing files retain their modes, and errors other than `EEXIST` propagate.\n * `DatabaseSync` reopens by path, so this does not protect confidentiality or\n * integrity when another principal can replace the database entry in its parent\n * directory.\n */\nasync function createDatabaseFile(path: string): Promise<void> {\n try {\n const handle = await open(path, \"wx\", 0o600);\n await handle.close();\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code !== \"EEXIST\") throw error;\n }\n}\n\n/**\n * Open the database and apply its schema and pragmas. An empty database with a\n * zero `user_version` is initialized at {@link SCHEMA_VERSION}; a nonempty\n * unversioned database and every other non-current version reject rather than\n * being migrated in place.\n * @param path - the SQLite database file to open (created when absent).\n * @param journalMode - validated journal pragma.\n * @param busyTimeout - milliseconds to wait for a contended write lock before\n * failing with `SQLITE_BUSY`; `0` fails immediately (SQLite's default).\n * @returns the open handle with pragmas applied and all tables ensured.\n */\nexport function openDatabase(\n path: string,\n journalMode: JournalMode,\n busyTimeout = DEFAULT_BUSY_TIMEOUT_MS,\n): DatabaseSync {\n const db = new DatabaseSync(path);\n try {\n configureDatabase(db, path, journalMode, busyTimeout);\n return db;\n } catch (error: unknown) {\n db.close();\n throw error;\n }\n}\n\nfunction configureDatabase(\n db: DatabaseSync,\n path: string,\n journalMode: JournalMode,\n busyTimeout: number,\n): void {\n // The remaining raw statements are driver-level SQLite operations with no\n // drizzle API: connection pragmas (foreign_keys / busy_timeout / journal_mode\n // / user_version / application_id) and the sqlite_schema system-table probe.\n db.exec(\"PRAGMA foreign_keys = ON\");\n // The busy timeout must precede every lock acquisition (the initialization\n // transaction below, and each write transaction in the backend) so a\n // contended database queues instead of failing one process's writes outright.\n db.exec(`PRAGMA busy_timeout = ${busyTimeout}`);\n const dbx = drizzle({ client: db });\n // Initialization runs in ONE drizzle transaction (`BEGIN IMMEDIATE` acquires\n // the write lock up front). Validate while holding the write lock so no other\n // connection can change schema ownership between inspection and init.\n dbx.transaction(\n (tx) => {\n const { user_version: onDisk } = tx.get(sql`PRAGMA user_version`) as {\n user_version: number;\n };\n const { application_id: applicationId } = tx.get(sql`PRAGMA application_id`) as {\n application_id: number;\n };\n const { count: userObjectCount } = tx.get(\n sql`SELECT COUNT(*) AS count FROM sqlite_schema WHERE name NOT GLOB 'sqlite_*'`,\n ) as { count: number };\n if (onDisk === 0 && (applicationId !== 0 || userObjectCount > 0)) {\n throw new Error(\n `session database at \"${path}\" has an unversioned schema or application identity`,\n );\n }\n if (onDisk !== 0 && onDisk !== SCHEMA_VERSION) {\n throw new Error(\n `session database at \"${path}\" has schema version ${onDisk}, incompatible with this build (${SCHEMA_VERSION})`,\n );\n }\n if (\n onDisk === SCHEMA_VERSION &&\n applicationId !== SESSION_PERSISTENCE_SQLITE_APPLICATION_ID\n ) {\n throw new Error(\n `session database at \"${path}\" has application id ${applicationId}, expected ${SESSION_PERSISTENCE_SQLITE_APPLICATION_ID}`,\n );\n }\n for (const statement of createTablesSql(\"sqlite\", sqliteTableDefs)) {\n tx.run(sql.raw(statement));\n }\n // The store identity row comes from the same drizzle table object; the\n // OR-IGNORE semantics are drizzle's `onConflictDoNothing` (the singleton\n // row already exists after the first open).\n tx.insert(tPersistenceState)\n .values({ fSingleton: 1, fStoreId: randomUUID() })\n .onConflictDoNothing()\n .run();\n if (onDisk === 0) {\n tx.run(sql.raw(`PRAGMA application_id = ${SESSION_PERSISTENCE_SQLITE_APPLICATION_ID}`));\n tx.run(sql.raw(`PRAGMA user_version = ${SCHEMA_VERSION}`));\n }\n },\n { behavior: \"immediate\" },\n );\n // The validated union is safe to interpolate into a non-bindable PRAGMA.\n // Apply it only after ownership validation and initialization commit.\n db.exec(`PRAGMA journal_mode = ${journalMode.toUpperCase()}`);\n}\n\n/** SQLite 后端的打开参数(来自配置的 sqlite 分支)。 */\nexport interface SqliteBackendOptions {\n path: string;\n journalMode: JournalMode;\n busyTimeout: number;\n}\n\n/**\n * SQLite 存储后端。打开时创建目录/文件(owner-only)、应用 pragma 与 DDL、\n * 校验 schema 版本/应用身份并读取 store id。\n */\nexport class SqliteBackend implements Backend {\n readonly kind = \"sqlite\" as const;\n storeIdentity!: string;\n\n /** The resolved database path (queue key); set by {@link open}. */\n private dbPath = \"\";\n private db!: SqliteDb;\n\n constructor(private readonly options: SqliteBackendOptions) {}\n\n async open(): Promise<void> {\n const actual =\n this.options.path === \":memory:\" ? this.options.path : resolve(this.options.path);\n this.dbPath = actual;\n if (actual !== \":memory:\") {\n await mkdir(dirname(actual), { recursive: true, mode: 0o700 });\n await createDatabaseFile(actual);\n }\n // The initialization transaction inside openDatabase is a synchronous\n // `BEGIN IMMEDIATE`, so it must queue behind the same per-path write queue\n // as the backend's own transactions: if another instance (sharing the\n // file) is mid-transaction and its async callback is yielding, a\n // synchronous BEGIN here would busy-wait and freeze the event loop until\n // busy_timeout — the other instance could never commit. Queuing turns that\n // \"open collides with an in-flight write\" race into a queue wait.\n await enqueueSqliteTx(actual, async () => {\n this.db = drizzle({\n client: openDatabase(actual, this.options.journalMode, this.options.busyTimeout),\n });\n });\n try {\n const row = this.db\n .select({ fStoreId: tPersistenceState.fStoreId })\n .from(tPersistenceState)\n .where(eq(tPersistenceState.fSingleton, 1))\n .get() as { fStoreId: string } | undefined;\n /* v8 ignore next -- openDatabase inserts the singleton before returning. */\n if (row === undefined) {\n throw new Error(`session database at \"${actual}\" has no store identity`);\n }\n if (row.fStoreId.length === 0) {\n throw new Error(`session database at \"${actual}\" has no valid store identity`);\n }\n if (actual !== \":memory:\") {\n const identity = statSync(actual, { bigint: true });\n this.storeIdentity = `file:${identity.dev}:${identity.ino}:${identity.birthtimeNs}:store:${row.fStoreId}`;\n } else {\n this.storeIdentity = `memory:store:${row.fStoreId}`;\n }\n } catch (error: unknown) {\n this.db.$client.close();\n throw error;\n }\n }\n\n async close(): Promise<void> {\n // `open` may have failed before assigning `db` (e.g. the queued init threw);\n // close must not crash the coordinator's dispose on top of that failure.\n if (this.db === undefined) return;\n this.db.$client.close();\n }\n\n async getSession(id: SessionId): Promise<SessionRow | undefined> {\n return this.db.select().from(tSessions).where(eq(tSessions.fSessionId, id)).get() as\n | SessionRow\n | undefined;\n }\n\n async getSeqMapRows(\n id: SessionId,\n ): Promise<Array<{ fSequence: number; fOriginalSeq: number; fKind: string }>> {\n return this.eventRows().where(eq(tSessionEvents.fSessionId, id)).all();\n }\n\n async getEventRows(id: SessionId, fromSequence?: number): Promise<EventRow[]> {\n const scoped =\n fromSequence === undefined\n ? this.eventRows().where(eq(tSessionEvents.fSessionId, id))\n : this.eventRows().where(\n and(eq(tSessionEvents.fSessionId, id), gte(tSessionEvents.fSequence, fromSequence)),\n );\n return scoped.orderBy(tSessionEvents.fSequence).all() as unknown as EventRow[];\n }\n\n async listSessions(): Promise<SessionRow[]> {\n return this.db.select().from(tSessions).all() as SessionRow[];\n }\n\n async transaction<T>(fn: (tx: BackendTx) => Promise<T>): Promise<T> {\n // drizzle's SQLite driver only supports SYNCHRONOUS transaction callbacks\n // (an async callback is rejected at the type level), while the shared\n // `BackendTx` interface is async because PostgreSQL is. The BEGIN/COMMIT/\n // ROLLBACK statements below are therefore driver-level and unavoidable.\n //\n // The async transaction callback also yields microtask gaps while the write\n // lock is held. A second connection in THIS process (another backend\n // instance sharing the file) that synchronously executes `BEGIN IMMEDIATE`\n // during such a gap would busy-wait on the lock and block the event loop,\n // so the lock holder could never commit (deadlock until busy_timeout).\n // Serializing the per-path write queue (see {@link enqueueSqliteTx})\n // removes the gap entirely — SQLite has one writer anyway, and a separate\n // process has its own event loop, so cross-process contention still\n // resolves through busy_timeout. A backend on a DIFFERENT database file has\n // no lock to contend for and is not serialized with this one.\n return enqueueSqliteTx(this.dbPath, async () => {\n this.db.$client.exec(\"BEGIN IMMEDIATE\");\n try {\n const result = await fn(this.tx);\n this.db.$client.exec(\"COMMIT\");\n return result;\n } catch (error: unknown) {\n // The DELETE+INSERT cannot collide; this rolls back a DB-level failure\n // (disk full, etc.), unreachable in test.\n /* v8 ignore start */\n try {\n this.db.$client.exec(\"ROLLBACK\");\n } catch {\n // The original SQLite failure remains the actionable cause.\n }\n throw error;\n /* v8 ignore stop */\n }\n });\n }\n\n /**\n * SQLite is a single connection: after `BEGIN IMMEDIATE` every query on the\n * same handle is inside the transaction, so the tx primitives are the same\n * row primitives used by the non-transactional reads.\n */\n private readonly tx: BackendTx = {\n upsertSession: (meta, incarnation) => this.upsertSession(meta, incarnation),\n getHead: (id) => this.getHead(id),\n insertEvents: (events) => this.insertEvents(events),\n insertBridges: (rows) => this.insertBridges(rows),\n updateHead: (id, headEventId, headSequence) => this.updateHead(id, headEventId, headSequence),\n bumpRevision: (id) => this.bumpRevision(id),\n deleteBridgeTail: (id, fromSequence) => this.deleteBridgeTail(id, fromSequence),\n getPrevBridge: (id, sequence) => this.getPrevBridge(id, sequence),\n getLastBridge: (id) => this.getLastBridge(id),\n updateEventFields: (id, sequence, fields) => this.updateEventFields(id, sequence, fields),\n };\n\n // --- row primitives (transaction-internal or standalone) ---\n\n private async upsertSession(meta: SessionHeader, incarnation: string): Promise<void> {\n this.db\n .insert(tSessions)\n .values(sessionInsertRow(meta, incarnation))\n .onConflictDoUpdate({\n target: tSessions.fSessionId,\n set: sessionConflictRow(meta),\n })\n .run();\n }\n\n private async getHead(\n id: SessionId,\n ): Promise<Pick<SessionRow, \"fHeadEventId\" | \"fHeadSequence\">> {\n const head = this.db\n .select({ fHeadEventId: tSessions.fHeadEventId, fHeadSequence: tSessions.fHeadSequence })\n .from(tSessions)\n .where(eq(tSessions.fSessionId, id))\n .get() as Pick<SessionRow, \"fHeadEventId\" | \"fHeadSequence\"> | undefined;\n /* v8 ignore next -- appendBatch/commitRepair always materialize the row before reading the head */\n if (head === undefined) throw new Error(`session \"${id}\" has no materialized row`);\n return head;\n }\n\n private async insertEvents(events: EventInsert[]): Promise<void> {\n if (events.length === 0) return;\n this.db\n .insert(tEvents)\n .values(events.map((event) => ({ ...event })))\n .run();\n }\n\n private async insertBridges(\n rows: Array<{ fSessionId: SessionId; fEventId: string; fSequence: number }>,\n ): Promise<void> {\n if (rows.length === 0) return;\n this.db\n .insert(tSessionEvents)\n .values(rows.map((row) => ({ ...row })))\n .run();\n }\n\n private async updateHead(\n id: SessionId,\n headEventId: string,\n headSequence: number,\n ): Promise<void> {\n this.db\n .update(tSessions)\n .set({ fHeadEventId: headEventId, fHeadSequence: headSequence })\n .where(eq(tSessions.fSessionId, id))\n .run();\n }\n\n private async bumpRevision(id: SessionId): Promise<void> {\n this.db\n .update(tSessions)\n .set({ fRevision: sql`${tSessions.fRevision} + 1` })\n .where(eq(tSessions.fSessionId, id))\n .run();\n }\n\n private async deleteBridgeTail(id: SessionId, fromSequence: number): Promise<void> {\n this.db\n .delete(tSessionEvents)\n .where(and(eq(tSessionEvents.fSessionId, id), gte(tSessionEvents.fSequence, fromSequence)))\n .run();\n }\n\n private async getPrevBridge(\n id: SessionId,\n sequence: number,\n ): Promise<{ fEventId: string; fSequence: number } | undefined> {\n return this.db\n .select({ fEventId: tSessionEvents.fEventId, fSequence: tSessionEvents.fSequence })\n .from(tSessionEvents)\n .where(and(eq(tSessionEvents.fSessionId, id), eq(tSessionEvents.fSequence, sequence)))\n .get() as { fEventId: string; fSequence: number } | undefined;\n }\n\n private async getLastBridge(\n id: SessionId,\n ): Promise<{ fEventId: string; fSequence: number } | undefined> {\n return this.db\n .select({ fEventId: tSessionEvents.fEventId, fSequence: tSessionEvents.fSequence })\n .from(tSessionEvents)\n .where(eq(tSessionEvents.fSessionId, id))\n .orderBy(desc(tSessionEvents.fSequence))\n .limit(1)\n .get() as { fEventId: string; fSequence: number } | undefined;\n }\n\n /** The joined event-row projection shared by whole-log and suffix reads. */\n private eventRows() {\n return this.db\n .select({\n fSequence: tSessionEvents.fSequence,\n fOriginalSeq: tEvents.fOriginalSeq,\n fKind: tEvents.fKind,\n fCreatedAt: tEvents.fCreatedAt,\n fData: tEvents.fData,\n fSourceEventSeqs: tEvents.fSourceEventSeqs,\n fSurfaceOp: tEvents.fSurfaceOp,\n })\n .from(tSessionEvents)\n .innerJoin(tEvents, eq(tSessionEvents.fEventId, tEvents.fEventId));\n }\n\n private async updateEventFields(\n id: SessionId,\n sequence: number,\n fields: {\n fSourceEventSeqs?: string | null;\n fSurfaceOp?: string | null;\n fData?: string;\n },\n ): Promise<void> {\n const eventIds = this.db\n .select({ fEventId: tSessionEvents.fEventId })\n .from(tSessionEvents)\n .where(and(eq(tSessionEvents.fSessionId, id), eq(tSessionEvents.fSequence, sequence)));\n this.db\n .update(tEvents)\n .set({\n ...(fields.fSourceEventSeqs === undefined\n ? {}\n : { fSourceEventSeqs: fields.fSourceEventSeqs }),\n ...(fields.fSurfaceOp === undefined ? {} : { fSurfaceOp: fields.fSurfaceOp }),\n ...(fields.fData === undefined ? {} : { fData: fields.fData }),\n })\n .where(inArray(tEvents.fEventId, eventIds))\n .run();\n }\n}\n","/**\n * PostgreSQL 存储后端:在 drizzle 的 PG async 驱动之上实现 {@link Backend}。\n * 驱动(`drizzle-orm/node-postgres` 生产 / `drizzle-orm/pglite` 测试)在构造\n * 时注入,因此本模块不依赖具体 PG 客户端包。\n *\n * 与 SQLite 的差异(方言事实,非行为差异):\n * - `f_created_at` 用 `BIGINT`(毫秒时间戳超出 PG `INTEGER` 的 int32 范围);\n * - schema 版本/应用身份校验用 `t_schema_meta` 键值表代替 SQLite 的\n * `PRAGMA user_version` / `application_id`(PG 无等价 pragma);\n * - 事务用 drizzle 的异步 `db.transaction`(PG 无 `BEGIN IMMEDIATE`,写锁靠\n * `busy_timeout` 之外的数据库行锁/唯一约束兜底)。\n * @module @morlay/session-rdb/postgres\n */\n\nimport { randomUUID } from \"node:crypto\";\nimport { and, desc, eq, gte, inArray, sql } from \"drizzle-orm\";\nimport type { PgAsyncDatabase, PgAsyncTransaction, PgQueryResultHKT } from \"drizzle-orm/pg-core\";\nimport type { SessionHeader, SessionId } from \"@deepseek-ai/dsh-session\";\nimport {\n type Backend,\n type BackendTx,\n type EventInsert,\n type EventRow,\n type SessionRow,\n} from \"./backend.ts\";\nimport { SCHEMA_VERSION, SESSION_PERSISTENCE_SQLITE_APPLICATION_ID } from \"./schema.ts\";\nimport { createTablesSql, toPostgresSchema } from \"./adapters/index.ts\";\nimport { postgresTableDefs } from \"./entities/index.ts\";\nimport { sessionConflictRow, sessionInsertRow } from \"./log.ts\";\n\n/**\n * PostgreSQL drizzle tables derived from the single entity definitions in\n * `src/entities/` (type safety carried by the hand-written row interfaces in\n * `backend.ts`, same as the SQLite side).\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nconst pgTables: Record<string, any> = toPostgresSchema(postgresTableDefs);\n\n/** `t_persistence_state` — the singleton row holding the store identity. */\nexport const pgPersistenceState = pgTables[\"t_persistence_state\"];\n\n/** `t_schema_meta` — PG's schema-version / application-identity key-value store. */\nexport const pgSchemaMeta = pgTables[\"t_schema_meta\"];\n\n/** `t_sessions` — the out-of-log metadata plus the playpen-style head cursor. */\nexport const pgSessions = pgTables[\"t_sessions\"];\n\n/** `t_events` — the globally addressable persisted event entity. */\nexport const pgEvents = pgTables[\"t_events\"];\n\n/** `t_session_events` — the session↔event bridge. */\nexport const pgSessionEvents = pgTables[\"t_session_events\"];\n\n/** PostgreSQL 后端的打开参数。 */\nexport interface PostgresBackendOptions {\n /** Revision 源限定前缀(不含 store id),如 `postgres:host:port:database`。 */\n identityBase: string;\n /** 关闭底层连接(生产 `pool.end()`;测试 `pglite.close()`)。 */\n close: () => Promise<void>;\n}\n\n/**\n * PostgreSQL 存储后端。构造时注入 drizzle PG 实例;{@link open} 建表并做\n * schema 版本/应用身份校验(`t_schema_meta`,替代 SQLite 的 PRAGMA)。\n */\nexport class PostgresBackend<THKT extends PgQueryResultHKT = PgQueryResultHKT> implements Backend {\n readonly kind = \"postgres\" as const;\n storeIdentity!: string;\n\n constructor(\n private readonly db: PgAsyncDatabase<THKT>,\n private readonly options: PostgresBackendOptions,\n ) {}\n\n async open(): Promise<void> {\n const storeId = await this.db.transaction(async (tx) => {\n // 探测必须在建表之前:t_schema_meta 存在与否区分「全新库」与「已有库」。\n // `to_regclass` 是数据库元数据查询,drizzle 没有对应 API。\n const probe = (await tx.execute(\n sql`SELECT to_regclass('t_schema_meta') IS NOT NULL AS exists`,\n )) as unknown as { rows: { exists: boolean }[] };\n const metaExists = probe.rows[0]?.exists === true;\n // DDL 由实体描述生成(一次一条:PG 的 extended query protocol 拒绝\n // 多语句字符串,且 DDL 事务性,逐条执行保持初始化原子)。\n for (const statement of createTablesSql(\"postgres\", postgresTableDefs)) {\n await tx.execute(sql.raw(statement));\n }\n if (!metaExists) {\n await tx\n .insert(pgSchemaMeta)\n .values([\n { fKey: \"schema_version\", fValue: String(SCHEMA_VERSION) },\n { fKey: \"application_id\", fValue: String(SESSION_PERSISTENCE_SQLITE_APPLICATION_ID) },\n ])\n .execute();\n }\n // 校验(缺版本行 = 有对象但未版本化 → 拒绝,不迁移)。\n const version = await this.readMeta(tx, \"schema_version\");\n const applicationId = await this.readMeta(tx, \"application_id\");\n if (version === undefined || applicationId === undefined) {\n throw new Error(\"session database has an unversioned schema or application identity\");\n }\n if (Number(version) !== SCHEMA_VERSION) {\n throw new Error(\n `session database has schema version ${version}, incompatible with this build (${SCHEMA_VERSION})`,\n );\n }\n if (Number(applicationId) !== SESSION_PERSISTENCE_SQLITE_APPLICATION_ID) {\n throw new Error(\n `session database has application id ${applicationId}, expected ${SESSION_PERSISTENCE_SQLITE_APPLICATION_ID}`,\n );\n }\n await tx\n .insert(pgPersistenceState)\n .values({ fSingleton: 1, fStoreId: randomUUID() })\n .onConflictDoNothing()\n .execute();\n const store = await tx\n .select({ fStoreId: pgPersistenceState.fStoreId })\n .from(pgPersistenceState)\n .where(eq(pgPersistenceState.fSingleton, 1))\n .execute();\n const storeId = store[0]?.fStoreId;\n if (storeId === undefined || storeId.length === 0) {\n throw new Error(\"session database has no valid store identity\");\n }\n return storeId;\n });\n this.storeIdentity = `${this.options.identityBase}:store:${storeId}`;\n }\n\n async close(): Promise<void> {\n await this.options.close();\n }\n\n async getSession(id: SessionId): Promise<SessionRow | undefined> {\n return (\n await this.db.select().from(pgSessions).where(eq(pgSessions.fSessionId, id)).execute()\n )[0] as SessionRow | undefined;\n }\n\n async getSeqMapRows(\n id: SessionId,\n ): Promise<Array<{ fSequence: number; fOriginalSeq: number; fKind: string }>> {\n return this.eventRows(this.db).where(eq(pgSessionEvents.fSessionId, id)).execute();\n }\n\n async getEventRows(id: SessionId, fromSequence?: number): Promise<EventRow[]> {\n const scoped =\n fromSequence === undefined\n ? this.eventRows(this.db).where(eq(pgSessionEvents.fSessionId, id))\n : this.eventRows(this.db).where(\n and(eq(pgSessionEvents.fSessionId, id), gte(pgSessionEvents.fSequence, fromSequence)),\n );\n return scoped.orderBy(pgSessionEvents.fSequence).execute() as unknown as EventRow[];\n }\n\n async listSessions(): Promise<SessionRow[]> {\n return this.db.select().from(pgSessions).execute() as unknown as Promise<SessionRow[]>;\n }\n\n async transaction<T>(fn: (tx: BackendTx) => Promise<T>): Promise<T> {\n return this.db.transaction(async (tx) => fn(this.txFor(tx)));\n }\n\n /** Bind the {@link BackendTx} primitives to one drizzle PG transaction handle. */\n private txFor(tx: PgAsyncTransaction<THKT>): BackendTx {\n return {\n upsertSession: (meta, incarnation) => this.upsertSession(tx, meta, incarnation),\n getHead: (id) => this.getHead(tx, id),\n insertEvents: (events) => this.insertEvents(tx, events),\n insertBridges: (rows) => this.insertBridges(tx, rows),\n updateHead: (id, headEventId, headSequence) =>\n this.updateHead(tx, id, headEventId, headSequence),\n bumpRevision: (id) => this.bumpRevision(tx, id),\n deleteBridgeTail: (id, fromSequence) => this.deleteBridgeTail(tx, id, fromSequence),\n getPrevBridge: (id, sequence) => this.getPrevBridge(tx, id, sequence),\n getLastBridge: (id) => this.getLastBridge(tx, id),\n updateEventFields: (id, sequence, fields) => this.updateEventFields(tx, id, sequence, fields),\n };\n }\n\n // --- meta helpers ---\n\n private async readMeta(exec: PgAsyncDatabase<THKT>, key: string): Promise<string | undefined> {\n const rows = await exec\n .select({ fValue: pgSchemaMeta.fValue })\n .from(pgSchemaMeta)\n .where(eq(pgSchemaMeta.fKey, key))\n .execute();\n return rows[0]?.fValue;\n }\n\n // --- row primitives (transaction-internal) ---\n\n private async upsertSession(\n exec: PgAsyncDatabase<THKT>,\n meta: SessionHeader,\n incarnation: string,\n ): Promise<void> {\n await exec\n .insert(pgSessions)\n .values(sessionInsertRow(meta, incarnation))\n .onConflictDoUpdate({\n target: pgSessions.fSessionId,\n set: sessionConflictRow(meta),\n })\n .execute();\n }\n\n private async getHead(\n exec: PgAsyncDatabase<THKT>,\n id: SessionId,\n ): Promise<Pick<SessionRow, \"fHeadEventId\" | \"fHeadSequence\">> {\n const head = (\n await exec\n .select({ fHeadEventId: pgSessions.fHeadEventId, fHeadSequence: pgSessions.fHeadSequence })\n .from(pgSessions)\n .where(eq(pgSessions.fSessionId, id))\n .execute()\n )[0] as Pick<SessionRow, \"fHeadEventId\" | \"fHeadSequence\"> | undefined;\n /* v8 ignore next -- appendBatch/commitRepair always materialize the row before reading the head */\n if (head === undefined) throw new Error(`session \"${id}\" has no materialized row`);\n return head;\n }\n\n private async insertEvents(exec: PgAsyncDatabase<THKT>, events: EventInsert[]): Promise<void> {\n if (events.length === 0) return;\n await exec\n .insert(pgEvents)\n .values(events.map((event) => ({ ...event })))\n .execute();\n }\n\n private async insertBridges(\n exec: PgAsyncDatabase<THKT>,\n rows: Array<{ fSessionId: SessionId; fEventId: string; fSequence: number }>,\n ): Promise<void> {\n if (rows.length === 0) return;\n await exec\n .insert(pgSessionEvents)\n .values(rows.map((row) => ({ ...row })))\n .execute();\n }\n\n private async updateHead(\n exec: PgAsyncDatabase<THKT>,\n id: SessionId,\n headEventId: string,\n headSequence: number,\n ): Promise<void> {\n await exec\n .update(pgSessions)\n .set({ fHeadEventId: headEventId, fHeadSequence: headSequence })\n .where(eq(pgSessions.fSessionId, id))\n .execute();\n }\n\n private async bumpRevision(exec: PgAsyncDatabase<THKT>, id: SessionId): Promise<void> {\n await exec\n .update(pgSessions)\n .set({ fRevision: sql`${pgSessions.fRevision} + 1` })\n .where(eq(pgSessions.fSessionId, id))\n .execute();\n }\n\n private async deleteBridgeTail(\n exec: PgAsyncDatabase<THKT>,\n id: SessionId,\n fromSequence: number,\n ): Promise<void> {\n await exec\n .delete(pgSessionEvents)\n .where(and(eq(pgSessionEvents.fSessionId, id), gte(pgSessionEvents.fSequence, fromSequence)))\n .execute();\n }\n\n private async getPrevBridge(\n exec: PgAsyncDatabase<THKT>,\n id: SessionId,\n sequence: number,\n ): Promise<{ fEventId: string; fSequence: number } | undefined> {\n return (\n await exec\n .select({ fEventId: pgSessionEvents.fEventId, fSequence: pgSessionEvents.fSequence })\n .from(pgSessionEvents)\n .where(and(eq(pgSessionEvents.fSessionId, id), eq(pgSessionEvents.fSequence, sequence)))\n .execute()\n )[0] as { fEventId: string; fSequence: number } | undefined;\n }\n\n private async getLastBridge(\n exec: PgAsyncDatabase<THKT>,\n id: SessionId,\n ): Promise<{ fEventId: string; fSequence: number } | undefined> {\n return (\n await exec\n .select({ fEventId: pgSessionEvents.fEventId, fSequence: pgSessionEvents.fSequence })\n .from(pgSessionEvents)\n .where(eq(pgSessionEvents.fSessionId, id))\n .orderBy(desc(pgSessionEvents.fSequence))\n .limit(1)\n .execute()\n )[0] as { fEventId: string; fSequence: number } | undefined;\n }\n\n /** The joined event-row projection shared by whole-log and suffix reads. */\n private eventRows(exec: PgAsyncDatabase<THKT>) {\n return exec\n .select({\n fSequence: pgSessionEvents.fSequence,\n fOriginalSeq: pgEvents.fOriginalSeq,\n fKind: pgEvents.fKind,\n fCreatedAt: pgEvents.fCreatedAt,\n fData: pgEvents.fData,\n fSourceEventSeqs: pgEvents.fSourceEventSeqs,\n fSurfaceOp: pgEvents.fSurfaceOp,\n })\n .from(pgSessionEvents)\n .innerJoin(pgEvents, eq(pgSessionEvents.fEventId, pgEvents.fEventId));\n }\n\n private async updateEventFields(\n exec: PgAsyncDatabase<THKT>,\n id: SessionId,\n sequence: number,\n fields: {\n fSourceEventSeqs?: string | null;\n fSurfaceOp?: string | null;\n fData?: string;\n },\n ): Promise<void> {\n const eventIds = exec\n .select({ fEventId: pgSessionEvents.fEventId })\n .from(pgSessionEvents)\n .where(and(eq(pgSessionEvents.fSessionId, id), eq(pgSessionEvents.fSequence, sequence)));\n await exec\n .update(pgEvents)\n .set({\n ...(fields.fSourceEventSeqs === undefined\n ? {}\n : { fSourceEventSeqs: fields.fSourceEventSeqs }),\n ...(fields.fSurfaceOp === undefined ? {} : { fSurfaceOp: fields.fSurfaceOp }),\n ...(fields.fData === undefined ? {} : { fData: fields.fData }),\n })\n .where(inArray(pgEvents.fEventId, eventIds))\n .execute();\n }\n}\n","/**\n * RDB 的「额外 provider 抽象」实现:在 `@morlay/session-rdb`\n * 既有 `PersistenceBackend`(append-only 面)之上,实现\n * `@morlay/session-branch` 的 `SessionBranchProvider`(分支面:rewind /\n * forkFrom / readBranchPrefix),并发布 `ctx.sessionBranch` 服务\n * (`SessionBranchRdb extends SessionBranch`)——使本包成为 rewind /\n * retry / fork 的持久化闭环。\n *\n * 关键设计(不改上游代码):\n *\n * - `forkFrom` 走**标准 coordinator 路径**(`create` + `append`):派生是纯\n * append(新 id / parentSession / seedLength),上游 `PersistenceCoordinator`\n * 天然支持;seed 前缀按新会话 log 重新编号(保序重编号,`sourceEventSeqs`\n * 的相对序不变,`source < seq` 校验成立)。\n * - `rewind` 是上游没有的原语(`PersistenceCoordinator` 只有 append-only +\n * torn-tail 修复),因此直接操作本后端的 `Backend` 事务(DELETE 尾部 +\n * head 游标回退 + revision bump),随后**重新 load 同步 coordinator 状态**\n * (revision 变化使 `isPreparedSourceCurrent` 失效 → 重新 adopt →\n * `state.cursor` 与新尾部一致)并更新 `WriteGuard` 确认 head。\n * - 独占条件:无 live owner(`ctx.sessions.get(id) === undefined`)。prepared\n * reservation 无法从公开 API 查询,rewind 通过 revision 变化使其自然失效。\n *\n * @module @morlay/session-rdb/branch\n */\n\nimport {\n SESSION_FORMAT_VERSION,\n type Session,\n type SessionEvent,\n type SessionHeader,\n type SessionId,\n} from \"@deepseek-ai/dsh-session\";\nimport type { SessionPersistenceSnapshot } from \"@deepseek-ai/dsh-session-persistence\";\nimport {\n SessionBranch,\n SessionBranchError,\n buildTimeline,\n type BranchAnchorMode,\n type BranchBoundary,\n type ForkFromOptions,\n type SessionBranchProvider,\n} from \"@morlay/session-branch\";\nimport { randomUUID } from \"node:crypto\";\nimport type { SessionPersistenceRdb } from \"./index.ts\";\nimport { rowToMeta } from \"./log.ts\";\nimport { isPersistedEvent } from \"./schema.ts\";\n\n/**\n * 定位 `atSeq` 锚定的闭合 `turn/end` 边界 seq。见\n * {@link SessionBranchProvider.readBranchPrefix} 的锚定语义说明。\n * @param events - 完整(或前缀)事件列表,seq 连续。\n * @param atSeq - 锚定 seq(inclusive);省略取最后闭合轮次。\n * @param mode - `\"after\"`(默认)或 `\"before\"`。\n * @returns 边界事件 seq;`\"before\"` 模式下 atSeq 之前无闭合轮次时返回 -1\n * (空前缀)。\n */\nexport function locateTurnEnd(\n events: readonly SessionEvent[],\n atSeq?: number,\n mode: BranchAnchorMode = \"after\",\n): number {\n const ends = events.filter((event) => event.type === \"turn/end\").map((event) => event.seq);\n if (atSeq === undefined) {\n const last = ends.at(-1);\n if (last === undefined) throw new SessionBranchError(\"session has no closed turn\", \"OPEN_TURN\");\n return last;\n }\n if (mode === \"before\") {\n let boundary = -1;\n for (const seq of ends) {\n if (seq < atSeq) boundary = seq;\n else break;\n }\n return boundary;\n }\n const firstAfter = ends.find((seq) => seq >= atSeq);\n if (firstAfter !== undefined) return firstAfter;\n // atSeq 越过末尾:若其落在未闭合轮内则拒绝,否则回退最后一个闭合轮。\n const lastStart = [...events].reverse().find((event) => event.type === \"turn/start\");\n if (lastStart !== undefined && lastStart.seq <= atSeq) {\n throw new SessionBranchError(`anchor ${atSeq} lies inside an open turn`, \"OPEN_TURN\");\n }\n const last = ends.at(-1);\n if (last === undefined) throw new SessionBranchError(\"session has no closed turn\", \"OPEN_TURN\");\n return last;\n}\n\n/** 按新会话 log 重新编号:保序重编号,`sourceEventSeqs` 相对序不变。 */\nfunction renumber(events: readonly SessionEvent[], offset: number): SessionEvent[] {\n return events.map((event, index) => ({ ...event, seq: offset + index }) as SessionEvent);\n}\n\n/** 派生会话 id(后端 mint 策略,与 apiproxy `session-<uuid>` 一致)。 */\nfunction mintSessionId(): SessionId {\n return `session-${randomUUID()}` as SessionId;\n}\n\n/**\n * live rewind 所需的「live 会话/agent」最小访问面。上游 `Session` 是\n * append-only、缓存增量式对象,无公开截断方法;`PersistenceCoordinator`\n * 的 `states` 是私有 Map。这些是**运行时最小侵入**(不改上游源码,仅在\n * 编排层操作编译后对象字段),由 `SessionBranchRdb` 注入。\n */\nexport interface LiveSessionHooks {\n /** 查当前 live 会话(`ctx.sessions.get`)。 */\n getSession(id: SessionId): Session | undefined;\n /** 查当前 live agent(`ctx.agents?.get`,agents 服务可选)。 */\n getAgent(id: SessionId): LiveAgentLike | undefined;\n /** 立即落盘 live 会话的 write-behind 缓冲(`ctx.sessions.flush`)。 */\n flush(session: Session): Promise<boolean>;\n /**\n * 截断后同步 coordinator 的会话内存状态:把 `states` 条目的 `cursor`\n * 对齐到新的尾部(= boundary + 1),使下一次 append 的 seq 连续性校验\n * 通过。**不能**删除 `states` 条目——live 会话的下一次 append 若走\n * `adopt` 会经 `SessionStore.prepare` 构造 detached session,与 store 中\n * 的 live entry 冲突。\n */\n setCoordinatorCursor(id: SessionId, cursor: number): void;\n /**\n * 截断后同步 coordinator 的 ownerless 状态(cold 分支):把 `states` 条目\n * 的 `cursor` 对齐到新尾部。与 {@link setCoordinatorCursor} 的区别是\n * **不要求条目已存在**——cold 会话可能从未被 adopt(无 states 条目),\n * 此时创建 ownerless 条目(meta 来自截断后快照),使下一次 append 走\n * 标准路径而非 `adopt`(adopt 会经 `prepareCore` 补 closers 撤销截断)。\n */\n setCoordinatorState(id: SessionId, cursor: number, meta: SessionHeader): void;\n}\n\n/** agent 的最小 live 形态:持有同一会话 + 可重置请求头日志标记。 */\nexport interface LiveAgentLike {\n session: Session;\n /** 编译后字段:是否已 append 过首个 `request/header`(截断后需重置)。 */\n requestHeaderLogged?: boolean;\n}\n\n/**\n * 截断 live 会话的内存 log 并重置全部派生缓存。上游 `Session` 的\n * `log`/`surfaceManager`/`headerFold`/`contextFold`/`derived` 都是增量缓存:\n * 截断 `log` 后必须同步重置,否则下一次 append 会基于陈旧状态校验/投影。\n * 字段名取编译后产物(`@deepseek-ai/dsh-session` lib),并对\n * `SurfaceManager`(持有同一 `log` 数组引用)做全量重折叠复位——\n * `_lastProcessedSeq = baseSeq - 1` 使下次访问重新 fold 整个截断后 log。\n */\nexport function truncateLiveSession(session: Session, newLength: number): void {\n const s = session as unknown as {\n log: SessionEvent[];\n eventsSnapshot?: unknown;\n headerFold?: unknown;\n headerFoldSeq: number;\n contextFold?: unknown;\n contextFoldSeq: number;\n derived: unknown[];\n derivedNodes: number;\n derivedGeneration: number;\n surfaceManager: {\n _state: { nodes: number[]; replaceGeneration: number };\n _lastProcessedSeq: number;\n _pendingPlan?: unknown;\n baseSeq: number;\n };\n };\n s.log.length = newLength;\n s.eventsSnapshot = undefined;\n s.headerFold = undefined;\n s.headerFoldSeq = 0;\n s.contextFold = undefined;\n s.contextFoldSeq = 0;\n s.derived = [];\n s.derivedNodes = 0;\n s.derivedGeneration = 0;\n s.surfaceManager._state = { nodes: [], replaceGeneration: 0 };\n s.surfaceManager._lastProcessedSeq = s.surfaceManager.baseSeq - 1;\n s.surfaceManager._pendingPlan = undefined;\n}\n\n/**\n * RDB 分支数据层实现(`SessionBranchProvider`)。与 `SessionPersistenceRdb`\n * 共享同一数据库连接(`Backend`)与 coordinator 写路径。\n */\nexport class SessionBranchRdbProvider implements SessionBranchProvider {\n readonly name = \"session-rdb\";\n\n constructor(\n private readonly persistence: SessionPersistenceRdb,\n /** live 会话/agent 访问面(rewind 支持 live session 时使用)。 */\n private readonly live: LiveSessionHooks = {\n getSession: () => undefined,\n getAgent: () => undefined,\n flush: async () => true,\n setCoordinatorCursor: () => {},\n setCoordinatorState: () => {},\n },\n ) {}\n\n async readBranchPrefix(\n id: SessionId,\n atSeq?: number,\n mode: BranchAnchorMode = \"after\",\n signal?: AbortSignal,\n ): Promise<BranchBoundary> {\n const { events } = await this.readRawEvents(id, signal);\n const boundary = locateTurnEnd(events, atSeq, mode);\n return { seq: boundary, events: events.slice(0, boundary + 1) };\n }\n\n /**\n * 读取会话的**原始**事件(不含 coordinator 补记的合成 closers)。\n *\n * `persistence.inspect` 走 coordinator 的 `prepareCore`,会给未闭合 log\n * 补 `interruptedTurnClosers`(合成 step/end + turn/end)——对普通读取\n * (客户端历史)这是正确的逻辑视图,但对分支编排是误导:未闭合轮次被\n * closers 掩盖成闭合,编辑未闭合轮次的 user 消息会走错边界。这里用\n * `loadStored`(backend hook,scanRows 只做 torn-tail 切割、不补 closers)\n * 读原始事件,使未闭合状态真实可见。\n */\n async readRawEvents(\n id: SessionId,\n signal?: AbortSignal,\n ): Promise<{ meta: SessionHeader; events: readonly SessionEvent[] }> {\n const stored = await this.persistence.loadStored(id, signal);\n if (stored === undefined)\n throw new SessionBranchError(`session \"${id}\" not found`, \"SESSION_NOT_FOUND\");\n return { meta: stored.meta, events: stored.events };\n }\n\n async forkFrom(\n sourceId: SessionId,\n options: ForkFromOptions = {},\n signal?: AbortSignal,\n ): Promise<SessionId> {\n signal?.throwIfAborted();\n const { atSeq, anchorMode = \"after\", seedSuffix = [], childSessionId, meta = {} } = options;\n const source = await this.persistence.inspect(sourceId, signal);\n const boundary = locateTurnEnd(source.events, atSeq, anchorMode);\n const prefix = source.events.slice(0, boundary + 1);\n const childId = childSessionId ?? mintSessionId();\n const childMeta: SessionHeader = {\n version: SESSION_FORMAT_VERSION,\n id: childId,\n createdAt: meta.createdAt ?? Date.now(),\n ...(meta.cwd !== undefined\n ? { cwd: meta.cwd }\n : source.meta.cwd !== undefined\n ? { cwd: source.meta.cwd }\n : {}),\n parentSession: sourceId,\n seedLength: prefix.length,\n ...(meta.agentPreset !== undefined\n ? { agentPreset: meta.agentPreset }\n : source.meta.agentPreset !== undefined\n ? { agentPreset: source.meta.agentPreset }\n : {}),\n ...(meta.origin !== undefined ? { origin: meta.origin } : {}),\n ...(meta.delegationDepth !== undefined ? { delegationDepth: meta.delegationDepth } : {}),\n };\n const seed = [...renumber(prefix, 0), ...renumber(seedSuffix, prefix.length)];\n await this.persistence.create(childMeta);\n if (seed.length > 0) await this.persistence.append(childId, seed);\n return childId;\n }\n\n async rewind(\n id: SessionId,\n toBoundary: number,\n signal?: AbortSignal,\n ): Promise<SessionPersistenceSnapshot> {\n signal?.throwIfAborted();\n if (!Number.isSafeInteger(toBoundary) || toBoundary < -1) {\n throw new SessionBranchError(\n `rewind boundary must be a non-negative safe integer, got ${toBoundary}`,\n \"INVALID_BOUNDARY\",\n );\n }\n const live = this.live.getSession(id);\n // live 会话先把 write-behind 缓冲全部落盘(RDB = live 内存全量),保证\n // 后续读取与后端事务(读 RDB head)基于同一视图。\n if (live !== undefined) await this.live.flush(live);\n // 边界校验:toBoundary 是已存在事件且为 turn/end 或 user/message。\n // 用**原始**事件(loadStored,不补 closers)——`inspect` 会经 coordinator\n // 的 `prepareCore` 给未闭合 log 补合成 step/end + turn/end,把 user/message\n // 边界掩盖成 turn/end,导致 exclusive 语义丢失。\n const raw = live === undefined ? await this.readRawEvents(id, signal) : undefined;\n const inspection = live === undefined ? undefined : await this.persistence.inspect(id, signal);\n const events = live === undefined ? raw!.events : inspection!.events;\n const meta = live === undefined ? raw!.meta : inspection!.meta;\n const boundaryEvent = events[toBoundary];\n if (toBoundary === -1) {\n // 空前缀:允许回退到「任何轮次之前」= 清空整个 log 的前置(head 归 -1)。\n // 与 commitRepair 的「head 回退到初始状态」语义一致。\n } else if (boundaryEvent === undefined) {\n throw new SessionBranchError(\n `rewind boundary ${toBoundary} does not exist in session \"${id}\"`,\n \"INVALID_BOUNDARY\",\n );\n } else if (boundaryEvent.type !== \"turn/end\" && boundaryEvent.type !== \"user/message\") {\n throw new SessionBranchError(\n `rewind boundary ${toBoundary} is not a turn/end or user/message (${boundaryEvent.type})`,\n \"INVALID_BOUNDARY\",\n );\n }\n // 保留前缀长度(上游坐标):turn/end 保留到该事件(inclusive);user/message\n // drop 该消息及其后(exclusive)——编辑重放语义,边界消息由编辑版替换。\n const keepLength =\n toBoundary === -1\n ? 0\n : boundaryEvent!.type === \"turn/end\"\n ? toBoundary + 1\n : toBoundary;\n\n const internals = this.persistence.internals();\n // —— 坐标换算:上游 seq(live log / 编排层 boundary)→ RDB 稠密 seq ——\n // live 会话的 `inspection.events` 是内存 log 的上游 seq:delta 事件\n // (assistant/chunk)与 ignorable 事件(版本效果)也占用连续 seq,而\n // RDB 只在写路径持久化 survived 事件并稠密重编号(f_sequence),\n // `head.fHeadSequence` 是稠密坐标。rewind 的 boundary 来自编排层基于\n // live log 计算的上游 seq,必须映射为 RDB 的删除目标 = 前缀中\n // persisted 事件数 - 1;否则「boundary > stored head」恒误报(上游\n // head 永远大于稠密 head)。cold 视图本身已是稠密 seq,直接使用。\n const denseBoundary =\n live === undefined\n ? keepLength - 1\n : events.slice(0, keepLength).filter(isPersistedEvent).length - 1;\n await internals.backend.transaction(async (tx) => {\n signal?.throwIfAborted();\n const head = await tx.getHead(id);\n if (denseBoundary > head.fHeadSequence) {\n throw new SessionBranchError(\n `rewind boundary ${toBoundary} is beyond the stored head ${head.fHeadSequence}`,\n \"INVALID_BOUNDARY\",\n );\n }\n if (denseBoundary < head.fHeadSequence) {\n await tx.deleteBridgeTail(id, denseBoundary + 1);\n const prev = denseBoundary === -1 ? undefined : await tx.getPrevBridge(id, denseBoundary);\n if (prev === undefined) {\n await tx.updateHead(id, \"\", -1);\n } else {\n await tx.updateHead(id, prev.fEventId, prev.fSequence);\n }\n }\n await tx.bumpRevision(id);\n });\n\n // 更新本实例的确认 head(下一次 append 的并发校验基准)——稠密坐标,\n // 与 `appendBatch` 内 `confirmHead` 的稠密语义一致。\n internals.writeGuard.confirmHead(id, denseBoundary);\n\n if (live !== undefined) {\n // live 分支:截断内存 log 并重置派生缓存;同步 coordinator 的\n // state.cursor(保留 owner,下一次 append 的 seq 校验续接截断后\n // 尾部);重置 agent 的请求头标记(截断可能删掉最后一条\n // request/header,让 agent 下次补记)。不调用 `load`——live 时 load\n // 会先 flush 把旧内存写回,撤销本次截断。\n truncateLiveSession(live, keepLength);\n const agent = this.live.getAgent(id);\n if (agent !== undefined) {\n agent.requestHeaderLogged = false;\n // 同步 agent 的轮次游标:截断后 session 的最后一个 turn 号。live\n // agent 驻留不重建,其 `phase.lastTurn` 仍是编辑前的值——不重置的话\n // 重放(followup)会继续递增产生**新**轮次号(如重试 turn 2 得到\n // turn 3),而不是复用目标轮号。\n const lastTurn = live.events.findLast((e) => e.type === \"turn/start\")?.data.turn ?? 0;\n const phase = (agent as unknown as { phase?: { lastTurn?: number } }).phase;\n if (phase !== undefined) phase.lastTurn = lastTurn;\n }\n this.live.setCoordinatorCursor(id, keepLength);\n } else {\n // cold 分支:同步 coordinator 状态。\n // - turn/end 边界:截断后 log 平衡(停在闭合轮次末尾),load 的\n // repair 为 no-op,经 revision 变化重新 adopt 使 state.cursor 与\n // 新尾部一致;\n // - user/message 边界:截断后 log 停在 turn/start(不平衡),load 的\n // `prepareCore` 会用 `interruptedTurnClosers` 补 closers 并 commitRepair\n // 落盘——把被 drop 的 user/message 的 seq 位置占掉,撤销截断语义。\n // 因此直接同步 ownerless states 条目(cursor = keepLength),下一次\n // append 走标准路径而非 adopt。\n if (boundaryEvent?.type === \"user/message\") {\n this.live.setCoordinatorState(id, keepLength, meta);\n } else {\n await this.persistence.load(id);\n }\n }\n\n const row = await internals.backend.getSession(id);\n if (row === undefined) {\n // 空会话(toBoundary = -1 且从未有行):返回「已确认缺席」快照。\n return {\n header: {\n version: SESSION_FORMAT_VERSION,\n id,\n createdAt: meta.createdAt,\n ...(meta.cwd !== undefined ? { cwd: meta.cwd } : {}),\n ...(meta.parentSession !== undefined ? { parentSession: meta.parentSession } : {}),\n ...(meta.seedLength !== undefined ? { seedLength: meta.seedLength } : {}),\n ...(meta.origin !== undefined ? { origin: meta.origin } : {}),\n ...(meta.delegationDepth !== undefined\n ? { delegationDepth: meta.delegationDepth }\n : {}),\n ...(meta.agentPreset !== undefined ? { agentPreset: meta.agentPreset } : {}),\n },\n revision:\n (await internals.readStoredRevision(id)) ??\n (await this.persistence.readStoredRevision(id))!,\n };\n }\n return { header: rowToMeta(row), revision: (await internals.readStoredRevision(id))! };\n }\n}\n\n/**\n * RDB 的 `ctx.sessionBranch` 服务:组合 {@link SessionBranchRdbProvider} 的\n * 数据层原语与共享版本树投影。插件把本类注册为 `sessionBranch` 服务后,\n * 编排层即可通过统一服务面完成 rewind / retry / fork。\n */\nexport class SessionBranchRdb extends SessionBranch {\n static inject = [\"sessionPersistence\", \"sessions\"];\n\n constructor(ctx: import(\"@deepseek-ai/cordis\").Context) {\n super(ctx);\n }\n\n private readonly provider = new SessionBranchRdbProvider(\n this.ctx.sessionPersistence as SessionPersistenceRdb,\n {\n getSession: (id) => this.ctx.sessions.get(id),\n getAgent: (id) => {\n // agents 服务可选(纯持久化环境无 agent-loop):经 ctx.get 动态访问。\n const agents = this.ctx.get(\"agents\") as\n | { get(id: SessionId): LiveAgentLike | undefined }\n | undefined;\n return agents?.get(id);\n },\n flush: (session) => this.ctx.sessions.flush(session),\n setCoordinatorCursor: (id, cursor) => {\n // coordinator 的 states 是私有 Map;保留条目(owner 不能丢),仅把\n // cursor 对齐到截断后的新尾部,使下一次 append 的 seq 校验续接。\n const persistence = this.ctx.sessionPersistence as unknown as {\n coordinator?: {\n states?: Map<SessionId, { cursor: number } | undefined>;\n };\n };\n const state = persistence.coordinator?.states?.get(id);\n if (state !== undefined) state.cursor = cursor;\n },\n setCoordinatorState: (id, cursor, meta) => {\n // cold 分支(user/message 边界):states 条目可能不存在(会话从未\n // 被 adopt)。存在则仅对齐 cursor;不存在则创建 ownerless 条目\n // (meta 来自截断后快照),使下一次 append 走标准路径而非 adopt\n // (adopt 会经 prepareCore 补 closers 撤销截断)。\n const persistence = this.ctx.sessionPersistence as unknown as {\n coordinator?: {\n states?: Map<SessionId, { cursor: number; meta: SessionHeader; materialized: boolean } | undefined>;\n };\n };\n const states = persistence.coordinator?.states;\n if (states === undefined) return;\n const state = states.get(id);\n if (state !== undefined) {\n state.cursor = cursor;\n } else {\n states.set(id, { meta, cursor, materialized: true });\n }\n },\n },\n );\n\n readBranchPrefix(\n id: SessionId,\n atSeq?: number,\n mode?: BranchAnchorMode,\n signal?: AbortSignal,\n ): Promise<BranchBoundary> {\n return this.provider.readBranchPrefix(id, atSeq, mode, signal);\n }\n\n /**\n * 读取会话的**原始**事件(不含 coordinator 补记的合成 closers)。\n * 编排层用它识别未闭合轮次(inspect 会把未闭合 log 补成闭合)。\n */\n readRawEvents(\n id: SessionId,\n signal?: AbortSignal,\n ): Promise<{ meta: SessionHeader; events: readonly SessionEvent[] }> {\n return this.provider.readRawEvents(id, signal);\n }\n\n forkFrom(\n sourceId: SessionId,\n options?: ForkFromOptions,\n signal?: AbortSignal,\n ): Promise<SessionId> {\n return this.provider.forkFrom(sourceId, options, signal);\n }\n\n rewind(\n id: SessionId,\n toBoundary: number,\n signal?: AbortSignal,\n ): Promise<SessionPersistenceSnapshot> {\n return this.provider.rewind(id, toBoundary, signal);\n }\n\n /**\n * 清洗一个 session 的 surface/provenance 坐标到稠密空间(持久化层透传)。\n * 旧数据(rewind 前写入)的 sourceEventSeqs / surfaceOp / shadowedRange\n * 是上游坐标,读取时每次都要对齐;清洗一次性写回稠密坐标,之后读取无需\n * 对齐。返回实际变更的事件数。\n */\n cleanseSession(sessionId: SessionId, signal?: AbortSignal): Promise<{ changed: number }> {\n return (this.ctx.sessionPersistence as SessionPersistenceRdb).cleanseSession(sessionId, signal);\n }\n\n /**\n * 同步 live 会话的 coordinator 内存 cursor,跳过 ignorable 占位事件。\n * 编排层在 rewind 后把 ignorable 版本效果 push 进 live log(不发布、不\n * 进缓冲)——它占用一个上游 seq,但 coordinator 的 cursor(rewind 设到\n * 截断后长度)看不见它;不跳过的话,flush 的 `appendLiveBatch` 会以\n * `e.seq >= cursor` 过滤掉 cursor 之前的事件(manualTurn 永远不落盘),\n * 或 `appendCore` 的 seq 连续性校验错位。这里把 cursor 从当前值起跳过\n * 连续的 ignorable 事件,对齐到下一个待持久化事件的 seq。\n */\n syncLiveCursor(sessionId: SessionId): void {\n const live = this.ctx.sessions.get(sessionId);\n if (live === undefined) return;\n const persistence = this.ctx.sessionPersistence as unknown as {\n coordinator?: {\n states?: Map<SessionId, { cursor: number } | undefined>;\n };\n };\n const state = persistence.coordinator?.states?.get(sessionId);\n if (state === undefined) return;\n let cursor = state.cursor;\n while (live.events[cursor]?.ignorable === true) cursor += 1;\n state.cursor = cursor;\n }\n\n async timeline(sessionId: SessionId, signal?: AbortSignal) {\n const persistence = this.ctx.sessionPersistence as SessionPersistenceRdb;\n const snapshots = await persistence.listSnapshots(signal);\n // live 会话从内存 log 读自有后缀(含 ignorable 版本效果事件);cold 会话\n // 走持久化 readFrom——版本效果不落 canonical log(ignorable 语义),\n // cold timeline 退化为 lineage 骨架(无效果详情)。\n const readOwnEvents = async (id: SessionId, fromSeq: number, s?: AbortSignal) => {\n const live = this.ctx.sessions.get(id);\n if (live !== undefined) return live.events.slice(fromSeq);\n return (await persistence.readFrom(id, fromSeq, s)).events;\n };\n return buildTimeline(snapshots, readOwnEvents, sessionId, signal);\n }\n}\n\nexport default SessionBranchRdb;\n","/**\n * SQLite / PostgreSQL durable session-persistence backend.\n *\n * Storage follows the playpen-session store: `t_sessions` carries the header\n * and a head cursor, `t_events` stores each event as a globally addressable\n * entity (event id + parent chain + kind/role/name/action-id dimensions), and\n * `t_session_events` bridges sessions to events in per-session seq order.\n * Delta content (`assistant/chunk`) is never persisted: those events are\n * dropped and the surviving events are re-numbered to a dense persisted seq\n * (`f_original_seq` keeps the upstream seq for provenance remapping).\n *\n * The database is chosen by configuration (discriminated union on `type`):\n * `{ type: \"sqlite\", path }` or `{ type: \"postgres\", connectionString }`.\n * All access goes through drizzle; the schema is declared once per dialect\n * (`schema.ts` / `postgres.ts`) and the hand-written DDL there is the only\n * migration story (no migration toolchain — incompatible stores are rejected,\n * never migrated).\n *\n * It delegates write-path orchestration to {@link PersistenceCoordinator} and\n * has no independent per-session artifact, so its locator returns `undefined`.\n * @module @morlay/session-rdb\n */\n\nimport { Context } from \"@deepseek-ai/cordis\";\nimport z from \"@deepseek-ai/schemastery\";\nimport { settingsNamespace, type SettingsProvider } from \"@deepseek-ai/dsh-settings\";\nimport { randomUUID } from \"node:crypto\";\nimport { Pool } from \"pg\";\nimport { drizzle as drizzlePg } from \"drizzle-orm/node-postgres\";\nimport {\n SessionPersistence,\n SessionPersistenceRevision,\n PersistenceCoordinator,\n type PersistenceBackend,\n type SessionLocation,\n type SessionPersistenceSnapshot,\n type StoredPrefix,\n type StoredSuffix,\n} from \"@deepseek-ai/dsh-session-persistence\";\nimport type {\n SessionEvent,\n SurfaceEventType,\n SessionId,\n SessionHeader,\n} from \"@deepseek-ai/dsh-session\";\nimport { type Backend, type BackendTx, type EventInsert, type EventRow } from \"./backend.ts\";\nimport { WriteGuard } from \"./write-guard.ts\";\nimport { buildSeqMap, normalizeSurfaceReplaceProvenance, rowToMeta, scanRows } from \"./log.ts\";\nimport {\n DEFAULT_BUSY_TIMEOUT_MS,\n eventDimensions,\n EVENT_ENCODING,\n isPersistedEvent,\n type JournalMode,\n} from \"./schema.ts\";\nimport { SqliteBackend } from \"./sqlite.ts\";\nimport { PostgresBackend } from \"./postgres.ts\";\nimport { SessionBranchRdb } from \"./branch.ts\";\n\nexport { SCHEMA_VERSION, EPHEMERAL_EVENT_TYPES } from \"./schema.ts\";\nexport { SessionBranchRdb, SessionBranchRdbProvider, locateTurnEnd } from \"./branch.ts\";\n\n/**\n * 同包分支 provider(rewind / forkFrom)访问 `SessionPersistenceRdb` 内部\n * 能力的窄接口。避免把 backend / writeGuard 暴露成公开 API,同时让\n * {@link SessionBranchRdbProvider} 与持久化后端共享同一连接与写路径。\n * @internal\n */\nexport interface SessionPersistenceRdbInternals {\n readonly backend: Backend;\n readonly writeGuard: WriteGuard;\n create(meta: SessionHeader): Promise<void>;\n append(id: SessionId, events: readonly SessionEvent[]): Promise<void>;\n load(id: SessionId): Promise<import(\"@deepseek-ai/dsh-session-persistence\").SessionInspection>;\n inspect(\n id: SessionId,\n signal?: AbortSignal,\n ): Promise<import(\"@deepseek-ai/dsh-session-persistence\").SessionInspection>;\n readFrom(\n id: SessionId,\n fromSeq: number,\n signal?: AbortSignal,\n ): Promise<{ meta: SessionHeader; events: SessionEvent[] }>;\n listSnapshots(signal?: AbortSignal): Promise<SessionPersistenceSnapshot[]>;\n readStoredRevision(\n id: SessionId,\n signal?: AbortSignal,\n ): Promise<import(\"@deepseek-ai/dsh-session-persistence\").SessionPersistenceRevision | undefined>;\n}\n\n/**\n * Plugin configuration — a discriminated union on `type`. The SQLite arm keeps\n * the file path plus the SQLite-specific pragmas; the PostgreSQL arm takes a\n * `node-postgres` connection string.\n */\nexport type Config =\n | {\n type: \"sqlite\";\n /**\n * Filesystem path to the SQLite database file. The special value `:memory:`\n * opens an in-process database (tests). On filesystems with POSIX modes,\n * missing directories and databases are created owner-only; existing path\n * modes are preserved.\n */\n path: string;\n /**\n * SQLite `journal_mode` pragma. `wal` (the default) is the recorded\n * durability model; pick a rollback-journal mode (`delete`/`truncate`/\n * `persist`) on filesystems where WAL's shared-memory files do not work\n * (network mounts). See {@link JournalMode}.\n */\n journalMode?: JournalMode;\n /**\n * Milliseconds to wait for a contended write lock before failing. SQLite\n * fails immediately by default, so a second process sharing this database\n * would lose every append that meets an in-flight commit; a nonzero wait\n * turns the contention window into a queue. `0` restores fail-fast.\n */\n busyTimeout?: number;\n }\n | {\n type: \"postgres\";\n /**\n * `node-postgres` connection string (e.g.\n * `postgres://user:pass@host:5432/db`). The database must be reachable;\n * the backend creates its tables and identity on first open.\n */\n connectionString: string;\n };\n\n/**\n * The persistence backend. Load as a plugin; it registers as\n * `ctx.sessionPersistence` and (via the coordinator) installs the write-path\n * listeners. Its torn-tail marker is the persisted seq to delete from.\n *\n * Configuration resolution: `$DSH_HOME/settings.yaml` 的\n * `session-rdb` namespace(settings 服务)覆盖 cordis 层 entry\n * config,见 {@link installSettingsSection}。\n */\nexport class SessionPersistenceRdb\n extends SessionPersistence\n implements PersistenceBackend<number>\n{\n static inject = [\"sessions\", \"settings\"];\n\n static Config: z<Config> = z.union([\n z.object({\n type: z.const(\"sqlite\"),\n path: z.string().required(),\n journalMode: z.union([\"wal\", \"delete\", \"truncate\", \"persist\"] as const).default(\"wal\"),\n busyTimeout: z.number().step(1).min(0).default(DEFAULT_BUSY_TIMEOUT_MS),\n }),\n z.object({\n type: z.const(\"postgres\"),\n connectionString: z.string().required(),\n }),\n ]);\n\n /** settings namespace:`$DSH_HOME/settings.yaml` 的 `session-rdb` section。 */\n static readonly settingsNs = settingsNamespace(\"session-rdb\");\n\n /**\n * Backend label for the coordinator's dispose diagnostics. Intentionally\n * shadows cordis `Service.name` (set to `'sessionPersistence'` by the base);\n * see the JSONL backend for why this does not affect service resolution.\n */\n override readonly name = \"session-rdb\";\n\n /** One RDB database holds every session; there is no per-session raw artifact. */\n override readonly supportsRawArtifacts = false;\n\n private readonly backend: Backend;\n private storeIdentity!: string;\n private readonly ready: Promise<void>;\n private readonly coordinator: PersistenceCoordinator<number>;\n /**\n * Write-authority state: the confirmed dense head per session (concurrent-\n * writer detection) and the dropped delta seqs per session (provenance\n * pruning). See {@link WriteGuard} for the timing contract.\n */\n private readonly writeGuard = new WriteGuard();\n\n constructor(\n ctx: Context,\n public config: Config,\n /**\n * @internal Test injection: use a pre-built backend (e.g. a drizzle PG\n * instance over an in-memory pglite) instead of {@link createBackend}.\n */\n injectedBackend?: Backend,\n ) {\n // settings.yaml 的 `session-rdb` namespace 覆盖 cordis 层 entry\n // config(base)。settings 服务已注册时(dsh 环境;服务注册完成即初始\n // publish 完成,见 SettingsProvider[Service.init])同步 register 读取;settings\n // 服务缺失时(纯 cordis 装配/测试)退化为 entry config。经 ctx.reflect\n // 查询避免未 inject 的 ctx 服务访问守卫。\n let resolved: Config = config;\n const settings = ctx.reflect.get(\"settings\") as unknown as SettingsProvider | undefined;\n if (settings !== undefined) {\n const scope = settings.register(\n SessionPersistenceRdb.settingsNs,\n SessionPersistenceRdb.Config,\n { base: config },\n );\n resolved = scope.get();\n scope.watch(() => {\n // 后端在构造时建成(数据库连接 + coordinator 写路径监听),settings\n // 变更后需重启 dsh 生效;热重建会与 coordinator 的持久状态冲突。\n ctx.logger.warn(\"session-rdb: settings changed; restart to apply the new configuration\");\n });\n }\n super(ctx);\n // Open asynchronously so connection setup (file creation / DB connect +\n // schema check) does not block plugin apply; every storage hook awaits the\n // same readiness promise.\n this.config = resolved;\n this.backend = injectedBackend ?? createBackend(resolved);\n this.ready = this.init();\n this.coordinator = new PersistenceCoordinator<number>(this.ctx, this);\n // 分支 provider 服务(闭环):本包同时实现 session-persistence 与\n // session-branch(rewind / forkFrom / timeline)。Service 构造即注册\n // ctx.sessionBranch 并随 fiber 卸载自动回滚。\n new SessionBranchRdb(this.ctx);\n }\n\n private async init(): Promise<void> {\n await this.backend.open();\n this.storeIdentity = this.backend.storeIdentity;\n }\n\n // --- SessionPersistence service surface (delegated to the coordinator) ---\n\n /** The backend has one database, not an independent local artifact per session. */\n locate(_meta: SessionHeader): SessionLocation | undefined {\n return undefined;\n }\n\n create(meta: SessionHeader): Promise<void> {\n return this.coordinator.create(meta);\n }\n\n append(id: SessionId, events: readonly SessionEvent[]): Promise<void> {\n return this.coordinator.append(id, events);\n }\n\n load(id: SessionId): Promise<{ meta: SessionHeader; events: readonly SessionEvent[] }> {\n return this.coordinator.load(id);\n }\n\n inspect(\n id: SessionId,\n signal?: AbortSignal,\n ): Promise<{ meta: SessionHeader; events: readonly SessionEvent[] }> {\n return this.coordinator.inspect(id, signal);\n }\n\n readFrom(\n id: SessionId,\n fromSeq: number,\n signal?: AbortSignal,\n ): Promise<{ meta: SessionHeader; events: SessionEvent[] }> {\n return this.coordinator.readFrom(id, fromSeq, signal);\n }\n\n // One method serves both public `list` and the backend hook; delegating it to\n // the coordinator would call this hook recursively.\n\n // --- PersistenceBackend hooks (the storage primitives) ---\n\n /** Read a stored prefix by id (ids are globally unique — no scope to scan). */\n loadStored(id: SessionId, signal?: AbortSignal): Promise<StoredPrefix<number> | undefined> {\n return this.readPrefix(id, signal);\n }\n\n /**\n * Seek-capable suffix read: the backend selects `f_sequence >= fromSeq`\n * directly, so the read scales with the suffix, not the log. Provenance\n * remapping still needs every row's upstream seq, so a lightweight\n * two-column map is read alongside. Torn rows past the preserved region are\n * dropped, never repaired (non-mutating read).\n */\n async loadStoredFrom(\n id: SessionId,\n fromSeq: number,\n signal?: AbortSignal,\n ): Promise<StoredSuffix | undefined> {\n const log = await this.readLog(id, { fromSeq }, signal);\n if (log === undefined) return undefined;\n return { meta: log.meta, events: log.events };\n }\n\n /**\n * Read a session's row + ordered events into a {@link StoredPrefix}. The\n * torn-tail marker is the persisted seq from which a never-committed tail\n * must be deleted (`scanRows` already returns it as `number | undefined`).\n * Records the confirmed dense head (or confirmed absence) so a later\n * `appendBatch` can detect a second writer that advanced the log.\n */\n private async readPrefix(\n id: SessionId,\n signal?: AbortSignal,\n ): Promise<StoredPrefix<number> | undefined> {\n const log = await this.readLog(id, {}, signal);\n if (log === undefined) {\n // Confirmed absence: a fresh session this instance has read about. A\n // later append to a session that meanwhile got a row must reject.\n this.writeGuard.confirmHead(id, -1);\n return undefined;\n }\n // The confirmed head is the last PRESERVED seq (a torn tail is removed by\n // the caller's commitRepair, which re-confirms the head after repair).\n this.writeGuard.confirmHead(id, log.events.at(-1)?.seq ?? -1);\n // 全量读取时使 replace 的 provenance 覆盖其 range(rewind 复用上游 seq\n // 空间后,保留的 checkpoint replace 可能引用已删除的行 → 读取时引用被\n // 剪,shadowed 校验失败 → 会话无法加载;这里把 range 内所有事件 seq 并入\n // provenance,见 {@link normalizeSurfaceReplaceProvenance})。\n normalizeSurfaceReplaceProvenance(log.events);\n return {\n meta: log.meta,\n events: log.events,\n // The revision must identify exactly these values and match\n // readStoredRevision's representation (see listSnapshots).\n revision: SessionPersistenceRevision(\n `${this.storeIdentity}:incarnation:${log.incarnation}:revision:${log.revision}`,\n ),\n ...(log.tornFrom !== undefined ? { tornMarker: log.tornFrom } : {}),\n };\n }\n\n /**\n * Read the current source-qualified revision for one stored session without\n * loading its event log. Returns `undefined` when the identity is absent.\n * The representation matches {@link loadStored}'s `revision` and\n * {@link listSnapshots} — the coordinator compares them with `===`.\n */\n async readStoredRevision(\n id: SessionId,\n signal?: AbortSignal,\n ): Promise<SessionPersistenceRevision | undefined> {\n signal?.throwIfAborted();\n await this.ready;\n signal?.throwIfAborted();\n const row = await this.backend.getSession(id);\n if (row === undefined) return undefined;\n return SessionPersistenceRevision(\n `${this.storeIdentity}:incarnation:${row.fIncarnation}:revision:${row.fRevision}`,\n );\n }\n\n /**\n * Shared read pipeline: session row → meta, event rows → preserved prefix.\n * A whole-log read (`fromSeq` absent) builds the seq map from the same rows;\n * a suffix read keeps the backend's lightweight two-column seq-map source so\n * the query still scales with the suffix, not the log.\n */\n private async readLog(\n id: SessionId,\n options: { fromSeq?: number } = {},\n signal?: AbortSignal,\n ): Promise<\n | {\n meta: SessionHeader;\n events: SessionEvent[];\n tornFrom?: number;\n /** The session row's stable identity (see {@link listSnapshots}). */\n incarnation: string;\n /** The session row's monotonic log-change token. */\n revision: number;\n }\n | undefined\n > {\n signal?.throwIfAborted();\n await this.ready;\n signal?.throwIfAborted();\n const row = await this.backend.getSession(id);\n if (row === undefined) return undefined;\n const meta = rowToMeta(row);\n let eventRows: EventRow[];\n let seqMap: ReadonlyMap<number, number> | undefined;\n let denseTypeMap: ReadonlyMap<number, string> | undefined;\n if (options.fromSeq === undefined) {\n // Whole-log read: build the seq map from the same rows (no extra query).\n eventRows = await this.backend.getEventRows(id);\n seqMap = buildSeqMap(eventRows);\n denseTypeMap = new Map(eventRows.map((r) => [r.fSequence, r.fKind]));\n } else {\n // Suffix read: rows are only the suffix, but provenance remapping needs\n // every row's upstream seq and dense type, so a lightweight two-column\n // map is read alongside — the query still scales with the suffix.\n eventRows = await this.backend.getEventRows(id, options.fromSeq);\n const seqRows = await this.backend.getSeqMapRows(id);\n seqMap = buildSeqMap(seqRows);\n denseTypeMap = new Map(seqRows.map((r) => [r.fSequence, r.fKind]));\n }\n signal?.throwIfAborted();\n const { preserved, tornFrom } = scanRows(eventRows, options.fromSeq ?? 0, seqMap, denseTypeMap);\n return {\n meta,\n events: preserved,\n incarnation: row.fIncarnation,\n revision: row.fRevision,\n ...(tornFrom !== undefined ? { tornFrom } : {}),\n };\n }\n\n /**\n * Durably append a batch in ONE transaction: materialize the sessions row (if\n * lazy) and INSERT every persisted event (plus its bridge row), or roll back\n * entirely. Delta events and events the writer marked `ignorable` are dropped\n * and the surviving events are re-numbered densely from the session's head\n * cursor; a batch that contains only dropped events is a no-op (no row\n * materialization, no revision bump). Dropped events' upstream seqs are\n * recorded per session so a later batch's surface provenance can prune\n * references to them (see {@link surfaceBindings}).\n * The transaction is the atomicity + durability boundary, so a mid-batch\n * failure (a UNIQUE violation on a duplicated seq) leaves the stored log\n * untouched.\n *\n * SQLite acquires the write lock up front (`BEGIN IMMEDIATE`, queued behind\n * `busy_timeout`); PostgreSQL relies on the transaction's row locks and the\n * `UNIQUE (f_session_id, f_sequence)` constraint to reject a colliding batch.\n * Either way {@link assertNoConcurrentWriter} rejects a second writer before\n * re-numbering — a session has exactly one writer per log, and a second\n * writer fails loud instead of corrupting the log.\n *\n * The row upsert runs UNCONDITIONALLY, not only when `!isMaterialized`: a\n * delta-only batch leaves the coordinator's materialized flag true while no\n * row exists, so the flag cannot be trusted as the row's existence signal.\n * The upsert keeps an existing row's head cursor (only header columns are\n * refreshed on conflict), so a fresh row still starts at the initial head.\n */\n async appendBatch(\n meta: SessionHeader,\n events: readonly SessionEvent[],\n _isMaterialized: boolean,\n ): Promise<void> {\n await this.ready;\n // Record every dropped delta's UPSTREAM seq (pure-delta batches included)\n // so a later batch's assistant/message can prune sourceEventSeqs references\n // to events that never got a persisted row (see {@link surfaceBindings}).\n const droppedSeqs = new Set<number>();\n for (const event of events) {\n if (!isPersistedEvent(event)) droppedSeqs.add(event.seq);\n }\n if (droppedSeqs.size > 0) this.writeGuard.noteDropped(meta.id, droppedSeqs);\n const persisted = events.filter(isPersistedEvent);\n if (persisted.length === 0) return;\n let confirmedHead = -1;\n await this.backend.transaction(async (tx) => {\n await tx.upsertSession(meta, randomUUID());\n const head = await tx.getHead(meta.id);\n // Reject a second writer BEFORE re-numbering: each coordinator instance\n // maintains its own upstream cursor, so a second instance (or process)\n // sharing this database would append through a stale view of the log —\n // the batch's events would be silently re-numbered onto the other\n // writer's tail and corrupt the log. The on-disk head must equal the\n // last head this instance confirmed (via its own writes or loadStored).\n this.writeGuard.assertNoConcurrentWriter(meta.id, head.fHeadSequence);\n const { headEventId, headSequence } = await appendEventTail(\n tx,\n meta,\n persisted,\n { parentId: head.fHeadEventId, nextSeq: head.fHeadSequence + 1 },\n (refs) => this.writeGuard.pruneRefs(meta.id, refs),\n );\n await tx.updateHead(meta.id, headEventId, headSequence);\n await tx.bumpRevision(meta.id);\n confirmedHead = headSequence;\n });\n // Confirm the new head only after the commit: a rollback must not leave\n // a confirmed head this instance did not actually write.\n this.writeGuard.confirmHead(meta.id, confirmedHead);\n }\n\n /**\n * Make a crash repair durable in ONE transaction: DELETE the torn tail (from\n * `tornMarker`), rewind the head cursor to the last surviving event, INSERT\n * the synthetic `closers`, and bump the revision once. After COMMIT the\n * stored rows == the balanced log.\n */\n async commitRepair(\n meta: SessionHeader,\n tornMarker: number | undefined,\n closers: readonly SessionEvent[],\n ): Promise<void> {\n await this.ready;\n const persistedClosers = closers.filter(isPersistedEvent);\n if (tornMarker === undefined && persistedClosers.length === 0) return;\n await this.backend.transaction(async (tx) => {\n if (tornMarker !== undefined) {\n await tx.deleteBridgeTail(meta.id, tornMarker);\n // The head cursor rewinds to the last surviving event (or the initial\n // state when the torn tail started at seq 0).\n const prev = await tx.getPrevBridge(meta.id, tornMarker - 1);\n if (prev === undefined) {\n await tx.updateHead(meta.id, \"\", -1);\n } else {\n await tx.updateHead(meta.id, prev.fEventId, prev.fSequence);\n }\n }\n if (persistedClosers.length > 0) {\n // Anchor at the ACTUAL tail row: the head cursor can lag the rows (a\n // hand-written torn tail never updated it), so a closer must follow the\n // last physical row, not the cursor.\n const last = await tx.getLastBridge(meta.id);\n const { headEventId, headSequence } = await appendEventTail(tx, meta, persistedClosers, {\n parentId: last?.fEventId ?? \"\",\n nextSeq: (last?.fSequence ?? -1) + 1,\n });\n await tx.updateHead(meta.id, headEventId, headSequence);\n }\n await tx.bumpRevision(meta.id);\n });\n // Re-confirm the head AFTER repair: truncation can rewind it and the\n // closers advance it, and the next append must not be rejected (or worse,\n // silently re-numbered) against a stale confirmation.\n const row = await this.backend.getSession(meta.id);\n this.writeGuard.confirmHead(meta.id, row?.fHeadSequence ?? -1);\n }\n\n /**\n * 一次性清洗一个 session 的 surface/provenance 坐标到稠密空间并写回。\n *\n * 旧数据(rewind 前的代码写入)的 `sourceEventSeqs`、`surfaceOp` replace\n * range 与 compaction `shadowedRange` 是上游坐标,读取时每次都要做坐标\n * 解析对齐;清洗用与读取完全相同的解析(稠密优先 + 上游映射 + 剪枝 +\n * replace provenance 补全)把它们重写为稠密坐标,之后读取无需再对齐\n * (对已清洗数据解析恒为恒等)。torn tail 片段不参与(读取路径也不会\n * 保留它们)。返回实际变更的事件数。\n */\n async cleanseSession(id: SessionId, signal?: AbortSignal): Promise<{ changed: number }> {\n signal?.throwIfAborted();\n await this.ready;\n signal?.throwIfAborted();\n const eventRows = await this.backend.getEventRows(id);\n if (eventRows.length === 0) return { changed: 0 };\n const seqMap = buildSeqMap(eventRows);\n const denseTypeMap = new Map(eventRows.map((r) => [r.fSequence, r.fKind]));\n const { preserved } = scanRows(eventRows, 0, seqMap, denseTypeMap);\n normalizeSurfaceReplaceProvenance(preserved);\n const bySeq = new Map(preserved.map((event) => [event.seq, event]));\n const updates: Array<{\n fSequence: number;\n fSourceEventSeqs?: string | null;\n fSurfaceOp?: string | null;\n fData?: string;\n }> = [];\n for (const row of eventRows) {\n const event = bySeq.get(row.fSequence);\n if (event === undefined) continue; // torn tail fragment\n const surface = event as SessionEvent & { sourceEventSeqs?: number[]; surfaceOp?: unknown };\n const newSeqs =\n surface.sourceEventSeqs === undefined ? null : JSON.stringify(surface.sourceEventSeqs);\n const newOp = surface.surfaceOp === undefined ? null : JSON.stringify(surface.surfaceOp);\n const fields: {\n fSourceEventSeqs?: string | null;\n fSurfaceOp?: string | null;\n fData?: string;\n } = {};\n if (newSeqs !== row.fSourceEventSeqs) fields.fSourceEventSeqs = newSeqs;\n if (newOp !== row.fSurfaceOp) fields.fSurfaceOp = newOp;\n // fData 只在 compaction 计量事件的 shadowedRange 变化时重写(避免\n // JSON 重序列化带来的无关变更)。\n if (row.fKind === \"compaction/summary\" || row.fKind === \"compaction/prune\") {\n const storedData = JSON.parse(row.fData) as { shadowedRange?: unknown };\n const newData = event.data as unknown as { shadowedRange?: unknown };\n if (JSON.stringify(storedData.shadowedRange) !== JSON.stringify(newData.shadowedRange)) {\n fields.fData = JSON.stringify(event.data);\n }\n }\n if (Object.keys(fields).length > 0) {\n updates.push({ fSequence: row.fSequence, ...fields });\n }\n }\n if (updates.length === 0) return { changed: 0 };\n await this.backend.transaction(async (tx) => {\n for (const update of updates) {\n await tx.updateEventFields(id, update.fSequence, {\n ...(update.fSourceEventSeqs === undefined\n ? {}\n : { fSourceEventSeqs: update.fSourceEventSeqs }),\n ...(update.fSurfaceOp === undefined ? {} : { fSurfaceOp: update.fSurfaceOp }),\n ...(update.fData === undefined ? {} : { fData: update.fData }),\n });\n }\n });\n return { changed: updates.length };\n }\n\n /** List all materialized sessions' metadata (every row is a materialized session). */\n async list(signal?: AbortSignal): Promise<SessionHeader[]> {\n signal?.throwIfAborted();\n await this.ready;\n signal?.throwIfAborted();\n const rows = await this.backend.listSessions();\n signal?.throwIfAborted();\n return rows.map(rowToMeta);\n }\n\n /** List metadata with a source-qualified monotonic revision per session. */\n async listSnapshots(signal?: AbortSignal): Promise<SessionPersistenceSnapshot[]> {\n signal?.throwIfAborted();\n await this.ready;\n signal?.throwIfAborted();\n const rows = await this.backend.listSessions();\n signal?.throwIfAborted();\n return rows.map((row) => ({\n header: rowToMeta(row),\n revision: SessionPersistenceRevision(\n `${this.storeIdentity}:incarnation:${row.fIncarnation}:revision:${row.fRevision}`,\n ),\n }));\n }\n\n /** Close the database connection (awaited by the coordinator's dispose, post-drain). */\n async close(): Promise<void> {\n await this.ready;\n await this.backend.close();\n }\n\n /**\n * 同包分支 provider 的内部访问面(rewind / forkFrom 共享后端与写路径)。\n * @internal 仅供 `SessionBranchRdbProvider` 使用;不是公开 API。\n */\n internals(): SessionPersistenceRdbInternals {\n return {\n backend: this.backend,\n writeGuard: this.writeGuard,\n create: (meta) => this.create(meta),\n append: (id, events) => this.append(id, events),\n load: (id) => this.load(id),\n inspect: (id, signal) => this.inspect(id, signal),\n readFrom: (id, fromSeq, signal) => this.readFrom(id, fromSeq, signal),\n listSnapshots: (signal) => this.listSnapshots(signal),\n readStoredRevision: (id, signal) => this.readStoredRevision(id, signal),\n };\n }\n}\n\n/**\n * Build the configured backend. The PostgreSQL arm creates the `node-postgres`\n * pool here (its identity base comes from the parsed pool options); tests\n * inject a drizzle PG instance directly via {@link PostgresBackend}.\n */\nfunction createBackend(config: Config): Backend {\n if (config.type === \"sqlite\") {\n return new SqliteBackend({\n path: config.path,\n journalMode: config.journalMode ?? \"wal\",\n busyTimeout: config.busyTimeout ?? DEFAULT_BUSY_TIMEOUT_MS,\n });\n }\n const pool = new Pool({ connectionString: config.connectionString });\n // node-postgres 官方要求 Pool 必须监听 error:idle client 被服务器端\n // 切断(数据库重启、DROP DATABASE ... FORCE)时,未监听的 error 会以\n // uncaughtException 崩溃进程。池级错误会在下一次操作(query)处可见,\n // 这里只需消费事件,无需输出。\n pool.on(\"error\", () => {});\n const db = drizzlePg({ client: pool });\n const identityBase = [\n \"postgres\",\n pool.options.host ?? \"localhost\",\n String(pool.options.port ?? 5432),\n pool.options.database ?? \"\",\n ].join(\":\");\n return new PostgresBackend(db, { identityBase, close: () => pool.end() });\n}\n\n/**\n * Serialize an event's surface-metadata fields for SQL binding. Both fields are\n * nullable TEXT columns — null when the event has no surface metadata (non-surface\n * events, events written before surface support).\n *\n * `sourceEventSeqs` references events by UPSTREAM seq. Delta events dropped at\n * write time never get a persisted row, so a reference to one can never be\n * remapped on read — keeping it verbatim produces a `source >= current seq`\n * provenance violation when the log is replayed as a session seed. The write\n * path therefore prunes references through {@link WriteGuard.pruneRefs}; a\n * fully pruned list is stored as null (no provenance).\n * @param event - the event to serialize.\n * @param prune - prunes references to dropped deltas before binding. Defaults\n * to identity (e.g. repair closers, which never carry provenance).\n */\nfunction surfaceBindings(\n event: SessionEvent,\n prune: (refs: readonly number[]) => readonly number[] = (refs) => refs,\n): [string | null, string | null] {\n const se = event as SessionEvent<SurfaceEventType>;\n const sourceSeqs = se.sourceEventSeqs === undefined ? undefined : prune(se.sourceEventSeqs);\n return [\n sourceSeqs !== undefined && sourceSeqs.length > 0 ? JSON.stringify(sourceSeqs) : null,\n se.surfaceOp !== undefined ? JSON.stringify(se.surfaceOp) : null,\n ];\n}\n\n/**\n * Durably append one batch of persisted events to a session's tail inside the\n * enclosing transaction: mint each event's row (parent chain + playpen\n * dimensions + surface-metadata columns) and its bridge row, land both as ONE\n * multi-row INSERT each (N events are 2 statements instead of 2N), and return\n * the resulting head cursor.\n *\n * The anchor is the caller's responsibility: a normal append starts from the\n * head cursor (`head.fHeadEventId` / `head.fHeadSequence + 1`), while\n * crash-repair closers start from the ACTUAL tail row (the head cursor can lag\n * a hand-written torn tail). Both callers then persist the returned cursor via\n * {@link BackendTx.updateHead}.\n * @param tx - the enclosing transaction.\n * @param meta - the session being written (`meta.id` drives the bridge rows).\n * @param events - persisted events to append (callers already filtered out\n * ephemeral/ignorable events; non-empty).\n * @param anchor - the parent event id to chain from and the next dense seq.\n * @param prune - forwarded to {@link surfaceBindings}; defaults to identity\n * (repair closers never carry provenance).\n * @returns the new head cursor (last event id + its dense seq).\n */\nasync function appendEventTail(\n tx: BackendTx,\n meta: SessionHeader,\n events: readonly SessionEvent[],\n anchor: { parentId: string; nextSeq: number },\n prune: (refs: readonly number[]) => readonly number[] = (refs) => refs,\n): Promise<{ headEventId: string; headSequence: number }> {\n let parentId = anchor.parentId;\n let nextSeq = anchor.nextSeq;\n // Build both batches up front, then land them in ONE multi-row INSERT each:\n // N events are 2 statements instead of 2N (fewer SQLite statements and fewer\n // PostgreSQL round trips per commit).\n const eventRows: EventInsert[] = [];\n const bridgeRows: Array<{ fSessionId: SessionId; fEventId: string; fSequence: number }> = [];\n for (const event of events) {\n const eventId = randomUUID();\n const { role, name, actionId } = eventDimensions(event);\n const [surfaceSeqs, surfaceOp] = surfaceBindings(event, prune);\n eventRows.push({\n fEventId: eventId,\n fParentId: parentId,\n fKind: event.type,\n fRole: role,\n fName: name,\n fActionId: actionId,\n fEncoding: EVENT_ENCODING,\n fData: JSON.stringify(event.data),\n fCreatedAt: event.time,\n fOriginalSeq: event.seq,\n fSourceEventSeqs: surfaceSeqs,\n fSurfaceOp: surfaceOp,\n });\n bridgeRows.push({ fSessionId: meta.id, fEventId: eventId, fSequence: nextSeq });\n parentId = eventId;\n nextSeq++;\n }\n await tx.insertEvents(eventRows);\n await tx.insertBridges(bridgeRows);\n return { headEventId: parentId, headSequence: nextSeq - 1 };\n}\n\nexport default SessionPersistenceRdb;\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.mjs","names":["buildColumn","sqliteCheck","text","integer","pgCheck","unique","index","drizzle","drizzlePg"],"sources":["../src/log.ts","../src/write-guard.ts","../src/entities/types.ts","../src/adapters/to-sqlite.ts","../src/adapters/to-postgres.ts","../src/adapters/ddl.ts","../src/entities/persistence-state.ts","../src/entities/schema-meta.ts","../src/entities/sessions.ts","../src/entities/events.ts","../src/entities/session-events.ts","../src/entities/index.ts","../src/schema.ts","../src/sqlite.ts","../src/postgres.ts","../src/branch.ts","../src/index.ts"],"sourcesContent":["/**\n * 会话 log 的表示转换:把持久化行(`t_sessions` / joined `t_session_events` +\n * `t_events` 行)转换为上游 `SessionHeader` / `SessionEvent`,以及反向的\n * 分类/映射辅助。全部为方言无关的纯函数,无 I/O——测试直接单测(见\n * `tests/rdb.spec.ts`)。\n *\n * delta 过滤(`assistant/chunk` 不落库)后,持久化 seq 是稠密重编号的;\n * `buildSeqMap` 提供 上游 seq → 持久化 seq 的映射,`rowToEvent` /\n * `remapSurfaceOp` / `remapShadowedRange` 经它把 `sourceEventSeqs`、\n * `replace` 范围与 compact 计量事件的 `shadowedRange` 重映射回稠密\n * seq 空间。`scanRows` 实现崩溃尾部语义(torn tail 切割 + 提交区损坏拒绝)。\n *\n * @module @morlay/session-rdb/log\n */\n\nimport type { SessionEvent, SessionHeader, SessionId, SurfaceOp } from \"@deepseek-ai/dsh-session\";\nimport type { EventRow, SessionRow } from \"./backend.ts\";\n\n/**\n * Reconstruct the {@link SessionHeader} from a `t_sessions` row.\n * @param row - the `t_sessions` table row.\n * @returns the header, `NULL` columns mapped to omitted optional fields.\n */\nexport function rowToMeta(row: SessionRow): SessionHeader {\n if (!Number.isSafeInteger(row.fCreatedAt) || row.fCreatedAt < 0) {\n throw new Error(\"stored session createdAt must be a non-negative safe integer\");\n }\n return {\n version: row.fVersion,\n id: row.fSessionId as SessionId,\n createdAt: row.fCreatedAt,\n ...(row.fCwd !== null ? { cwd: row.fCwd } : {}),\n ...(row.fParentSession !== null ? { parentSession: row.fParentSession as SessionId } : {}),\n ...(row.fSeedLength !== null ? { seedLength: row.fSeedLength } : {}),\n ...(row.fOrigin !== null ? { origin: row.fOrigin as \"subagent\" } : {}),\n ...(row.fDelegationDepth === null ? {} : { delegationDepth: row.fDelegationDepth }),\n };\n}\n\n/**\n * `t_sessions` 的 INSERT 列值:`SessionHeader` 的持久化字段 + 初始 head 游标\n * (空事件 id、seq -1)+ materialization identity(`f_incarnation`)+ revision 0。\n * 方言无关的纯映射——SQLite 与 PostgreSQL 后端的 `upsertSession` 共用,列名\n * 与 `src/entities/sessions.ts` 对齐(改一处即两方言生效)。`f_id` serial 由\n * 数据库生成,不在映射内。\n */\nexport function sessionInsertRow(\n meta: SessionHeader,\n incarnation: string,\n): {\n fSessionId: string;\n fHeadEventId: string;\n fHeadSequence: number;\n fVersion: number;\n fCreatedAt: number;\n fCwd: string | null;\n fParentSession: string | null;\n fSeedLength: number | null;\n fOrigin: string | null;\n fDelegationDepth: number | null;\n fIncarnation: string;\n fRevision: number;\n} {\n return {\n fSessionId: meta.id,\n fHeadEventId: \"\",\n fHeadSequence: -1,\n fVersion: meta.version,\n fCreatedAt: meta.createdAt,\n fCwd: meta.cwd ?? null,\n fParentSession: meta.parentSession ?? null,\n fSeedLength: meta.seedLength ?? null,\n fOrigin: meta.origin ?? null,\n fDelegationDepth: meta.delegationDepth ?? null,\n fIncarnation: incarnation,\n fRevision: 0,\n };\n}\n\n/**\n * `t_sessions` 的 ON CONFLICT 更新列值:只刷新 header 列,保留 head 游标\n * (`f_head_event_id`/`f_head_sequence`)与 materialization identity\n * (`f_incarnation`/`f_revision`)。方言无关,两后端共用(见\n * {@link sessionInsertRow})。\n */\nexport function sessionConflictRow(meta: SessionHeader): {\n fVersion: number;\n fCreatedAt: number;\n fCwd: string | null;\n fParentSession: string | null;\n fSeedLength: number | null;\n fOrigin: string | null;\n fDelegationDepth: number | null;\n} {\n return {\n fVersion: meta.version,\n fCreatedAt: meta.createdAt,\n fCwd: meta.cwd ?? null,\n fParentSession: meta.parentSession ?? null,\n fSeedLength: meta.seedLength ?? null,\n fOrigin: meta.origin ?? null,\n fDelegationDepth: meta.delegationDepth ?? null,\n };\n}\n\n/**\n * Remap a stored {@link SurfaceOp} from upstream seqs to persisted seqs. An\n * `append` op carries no seqs; a positional `replace`'s `start`/`end` name\n * surface nodes by UPSTREAM seq and must follow {@link SessionEvent.sourceEventSeqs}\n * through the same upstream→persisted map when delta filtering re-numbered the\n * log — otherwise the replacement range is looked up against DENSE seqs and the\n * surface fold rejects the log (\"start seq N not found in surface\").\n * @param op - the stored surface op.\n * @param remap - upstream→persisted seq mapping (identity when absent).\n * @returns the remapped surface op.\n */\nexport function remapSurfaceOp(op: SurfaceOp, remap: (seq: number) => number): SurfaceOp {\n if (op === \"append\") return op;\n return { op: \"replace\", start: remap(op.start), end: remap(op.end) };\n}\n\n/**\n * The compact metering events (`compaction/summary`, `compaction/prune`) carry the\n * token-meter's shadow-price claim in `data.shadowedRange`: the inclusive\n * surface-node seqs of the range the IMMEDIATELY following surface `replace`\n * shadows. The range names surface nodes by UPSTREAM seq, so it must follow\n * the replace's `surfaceOp` through the same upstream→persisted map — the\n * fold compares claim and replacement ranges for exact equality, and an\n * un-remapped claim (upstream) next to a remapped replacement range (dense)\n * makes replay fail loud (\"token surface: replace ... has no adjacent shadow\n * price\").\n * @param range - the stored shadowed range (upstream seqs).\n * @param remap - upstream→persisted seq mapping (identity when absent).\n * @returns the remapped shadowed range.\n */\nexport function remapShadowedRange(\n range: { start: number; end: number },\n remap: (seq: number) => number,\n): { start: number; end: number } {\n return { start: remap(range.start), end: remap(range.end) };\n}\n\n/**\n * Reconstruct a {@link SessionEvent} from a joined row. The emitted event\n * carries the DENSE persisted seq (`row.fSequence`); `sourceEventSeqs` entries,\n * a positional `replace` {@link SurfaceOp}'s range, and the compact metering\n * events' `shadowedRange` are resolved onto persisted seqs through `seqMap`\n * when the log was delta-filtered.\n *\n * Coordinate resolution is \"the stored f_sequence wins, when it is a valid\n * earlier reference\": after resume the upstream live log IS the dense log,\n * so events written since (checkpoint replaces, provenance) cite DENSE seqs.\n * A cited seq that exists in the dense space, precedes this event, and is\n * either a SURFACE node or has no upstream counterpart is kept as-is. A seq\n * outside the dense space (or a dense seq that is a non-surface event with an\n * upstream counterpart — an upstream-cited seq that happens to collide with a\n * later dense seq) is treated as an UPSTREAM reference and remapped through\n * the upstream→persisted map, again only when the result precedes this event.\n * Anything else is unresolvable and dropped (never kept verbatim — after\n * rewind re-uses the upstream space, keeping an upstream value would mix\n * coordinates and fail the Session seed validation).\n * @param row - the joined `t_session_events` + `t_events` row.\n * @param seqMap - upstream→persisted seq map (optional).\n * @param denseTypeMap - every persisted f_sequence → event type for the\n * session (optional; always passed together with `seqMap`).\n * @returns the reconstructed event; throws when a JSON column fails to parse\n * ({@link scanRows} treats that as a hole, not corruption, in the tail).\n */\nexport function rowToEvent(\n row: EventRow,\n seqMap?: ReadonlyMap<number, number>,\n denseTypeMap?: ReadonlyMap<number, string>,\n): SessionEvent {\n // Surface-metadata fields are conditional on the event type in the type\n // system; spread them so each variant gets only the fields it declares.\n // `sourceEventSeqs` must never mix coordinates: an unresolvable reference is\n // dropped, and the survivors are sorted/de-duplicated — see\n // {@link remapProvenance}. A fully pruned list is omitted entirely (the\n // event carries no provenance), matching the write path's null storage.\n const remap = (seq: number): number =>\n resolveProvenanceSeq(seq, row.fSequence, seqMap, denseTypeMap) ?? seq;\n const strictRemap = (seq: number): number | undefined =>\n resolveProvenanceSeq(seq, row.fSequence, seqMap, denseTypeMap);\n const surfaceFields = {\n ...(row.fSourceEventSeqs !== null\n ? (() => {\n const refs = remapProvenance(JSON.parse(row.fSourceEventSeqs) as number[], strictRemap);\n return refs.length > 0 ? { sourceEventSeqs: refs } : {};\n })()\n : {}),\n ...(row.fSurfaceOp !== null\n ? {\n surfaceOp: remapSurfaceOp(JSON.parse(row.fSurfaceOp) as SurfaceOp, remap),\n }\n : {}),\n };\n const data = JSON.parse(row.fData) as SessionEvent[\"data\"];\n // `compaction/summary` / `compaction/prune` are plugin-merged types whose\n // metering data is not part of the core `SessionEventMap`; narrow through a\n // structural view to remap the shadow-price claim's range (see\n // {@link remapShadowedRange}).\n if (row.fKind === \"compaction/summary\" || row.fKind === \"compaction/prune\") {\n const metering = data as unknown as { shadowedRange?: { start: number; end: number } };\n if (metering.shadowedRange !== undefined) {\n metering.shadowedRange = remapShadowedRange(metering.shadowedRange, remap);\n }\n }\n return {\n type: row.fKind as SessionEvent[\"type\"],\n seq: row.fSequence,\n time: row.fCreatedAt,\n data,\n ...surfaceFields,\n } as SessionEvent;\n}\n\n/**\n * Resolve one cited seq onto the DENSE persisted seq space.\n *\n * \"The stored f_sequence wins, when it is a valid earlier reference\": after\n * resume the upstream live log IS the dense log, so events written since\n * (checkpoint replaces, provenance) cite DENSE seqs. A cited seq that exists\n * in the dense space, precedes `eventSeq`, and is either a SURFACE node or has\n * no upstream counterpart is kept as-is. A seq outside the dense space (or a\n * dense seq that is a non-surface event with an upstream counterpart — an\n * upstream-cited seq that happens to collide with a later dense seq) is\n * treated as an UPSTREAM reference and remapped through the upstream→persisted\n * map, again only when the result precedes `eventSeq`. Anything else is\n * unresolvable (`undefined`): provenance entries are dropped rather than kept\n * verbatim, because after rewind re-uses the upstream space an upstream value\n * would mix coordinates and fail the Session seed validation.\n * @param seq - the cited seq (upstream or dense).\n * @param eventSeq - the citing event's dense seq.\n * @param seqMap - upstream→persisted seq map (optional).\n * @param denseTypeMap - every persisted f_sequence → event type (optional;\n * always passed together with `seqMap`).\n * @returns the dense seq to cite, or undefined when unresolvable.\n */\nexport function resolveProvenanceSeq(\n seq: number,\n eventSeq: number,\n seqMap?: ReadonlyMap<number, number>,\n denseTypeMap?: ReadonlyMap<number, string>,\n): number | undefined {\n if (seqMap === undefined && denseTypeMap === undefined) return seq;\n const kind = denseTypeMap?.get(seq);\n if (\n kind !== undefined &&\n seq < eventSeq &&\n (SURFACE_EVENT_TYPES.has(kind) || !seqMap?.has(seq))\n ) {\n return seq;\n }\n const dense = seqMap?.get(seq);\n if (dense !== undefined && dense < eventSeq) return dense;\n return undefined;\n}\n\n/**\n * Build the upstream→persisted seq map for one session's persisted events\n * (only meaningful when delta filtering re-numbered the log).\n *\n * A session re-opened by resume (or forked) persists the seed segment and the\n * new segment in ONE log: the seed rows keep the PARENT session's upstream seqs\n * while the resumed rows carry the child session's own upstream seqs, which\n * renumber from the seed boundary and therefore OVERLAP the parent space. The\n * FIRST mapping wins so a seed-segment event's provenance reference resolves to\n * the seed-space row it actually derived from (rows are ordered by persisted\n * seq, so the seed segment always precedes the resumed one); the resumed\n * segment's references are unique within their own space unless they point at a\n * shared value, which only the parent could have produced first.\n * @param rows - one session's seq rows (upstream + persisted), ordered by seq\n * ascending (only the two seq columns are needed).\n * @returns map from `f_original_seq` to `f_sequence` (first occurrence wins).\n */\nexport function buildSeqMap(\n rows: readonly Pick<EventRow, \"fSequence\" | \"fOriginalSeq\">[],\n): Map<number, number> {\n const map = new Map<number, number>();\n for (const row of rows) {\n if (!map.has(row.fOriginalSeq)) map.set(row.fOriginalSeq, row.fSequence);\n }\n return map;\n}\n\n/**\n * Normalize a stored provenance list into the persisted seq space.\n *\n * `sourceEventSeqs` references events by UPSTREAM seq; on read each entry is\n * remapped to the DENSE persisted seq through the upstream→persisted map. An\n * entry whose event never got a persisted row (a dropped delta) or no longer\n * has one cannot be remapped. Keeping such an entry verbatim would mix\n * upstream and dense coordinates in one list — after rewind re-uses the\n * upstream seq space, a stale reference can even collide with a live dense\n * seq — which the upstream Session seed validation rejects (duplicates /\n * non-monotonic / \"must reference earlier events\"). Unmappable entries are\n * dropped. Because the upstream space is reused, the remapped entries can\n * also arrive out of order or duplicated; the result is sorted and\n * de-duplicated so it is strictly increasing, as the upstream provenance\n * contract requires. The output's SET (the only thing surface-replace\n * shadowing checks) is unchanged by normalization.\n * @param refs - the event's `sourceEventSeqs` (upstream seqs).\n * @param remap - upstream→persisted seq mapping; undefined for an entry\n * whose event has no persisted row.\n * @returns the normalized, strictly increasing persisted seqs (possibly empty).\n */\nexport function remapProvenance(\n refs: readonly number[],\n remap: (seq: number) => number | undefined,\n): number[] {\n const mapped: number[] = [];\n for (const ref of refs) {\n const dense = remap(ref);\n if (dense !== undefined) mapped.push(dense);\n }\n mapped.sort((a, b) => a - b);\n const result: number[] = [];\n for (const seq of mapped) {\n if (result.length === 0 || result[result.length - 1] !== seq) result.push(seq);\n }\n return result;\n}\n\n/**\n * The event types that produce model-visible surface nodes (mirror of the\n * upstream `SURFACE_EVENT_TYPES`); only these can appear in a replace's\n * shadowed range, so only their seqs may be merged into provenance.\n */\nconst SURFACE_EVENT_TYPES = new Set([\"user/message\", \"assistant/message\", \"tool/result\"]);\n\n/**\n * Make a surface `replace` event's provenance cover its replacement range.\n *\n * The upstream Session seed validation requires a replace's `sourceEventSeqs`\n * to include every surface node it shadows (`assertProvenance`'s shadowed\n * check). Rewind re-uses the upstream seq space: a replace that survives a\n * truncation (e.g. a checkpoint `user/message` before the new boundary) has\n * its range remapped onto DENSE seqs, while its provenance references were\n * written in upstream seqs — references to rows rewind deleted are dropped on\n * read, so the surviving provenance no longer covers the range's dense nodes.\n * Merging every persisted SURFACE node seq inside the (dense) range into the\n * provenance makes the log loadable again; extra references are legal\n * provenance and never affect the fold (the fold consumes only the surfaceOp\n * range). Only events the map could resolve exist in the dense range, so the\n * merged list is strictly increasing after sort/dedup.\n * @param events - a session's dense event list (contiguous seqs), read in\n * full; mutated in place.\n */\nexport function normalizeSurfaceReplaceProvenance(events: SessionEvent[]): void {\n for (const event of events) {\n const raw = event as SessionEvent & { surfaceOp?: unknown; sourceEventSeqs?: number[] };\n const op = raw.surfaceOp;\n if (typeof op !== \"object\" || op === null || (op as { op?: string }).op !== \"replace\") {\n continue;\n }\n const { start, end } = op as { start: number; end: number };\n const refs = new Set(raw.sourceEventSeqs ?? []);\n for (const candidate of events) {\n if (\n candidate.seq >= start &&\n candidate.seq <= end &&\n SURFACE_EVENT_TYPES.has(candidate.type)\n ) {\n refs.add(candidate.seq);\n }\n }\n raw.sourceEventSeqs = [...refs].sort((a, b) => a - b);\n }\n}\n\n/**\n * Prune `sourceEventSeqs` references that cannot be remapped on read.\n *\n * `sourceEventSeqs` references events by UPSTREAM seq; on read the references\n * are remapped to the DENSE persisted seq through {@link buildSeqMap}. A\n * reference whose event never got a persisted row (a dropped delta, or a seq\n * that never existed) has no map entry and would replay as a `source >=\n * current seq` provenance violation, so the write path prunes it. A fully\n * pruned list is stored as null (no provenance) by the serializer.\n *\n * The `keep` predicate is the CALLER's view of resolvability: the write path\n * knows which upstream seqs THIS INSTANCE dropped (`WriteGuard.pruneRefs` —\n * per-instance knowledge, which must not prune references to rows another\n * instance persisted, e.g. a resume seed segment), while the one-shot repair\n * script knows which upstream seqs exist on DISK (full-database view). The\n * filter itself is shared so the rule lives in one place.\n * @param refs - the event's `sourceEventSeqs` (upstream seqs).\n * @param keep - true for a seq whose referenced event is resolvable.\n * @returns the pruned list.\n */\nexport function pruneSourceEventSeqs(\n refs: readonly number[],\n keep: (seq: number) => boolean,\n): number[] {\n return refs.filter(keep);\n}\n\n/**\n * Find the preserved prefix of ordered event rows. Fully written rows in an\n * interrupted final turn remain in the prefix. The first unparsable row or seq\n * gap after the last `turn/end` marks a tolerated torn tail; the same hole in\n * the committed region rejects.\n *\n * @param rows - one session's event rows, ordered by persisted seq ascending.\n * @param base - the persisted seq the first row is expected to carry; `0` for\n * a whole log, the requested `fromSeq` for a suffix read (`loadStoredFrom`).\n * @param seqMap - upstream→persisted seq map forwarded to {@link rowToEvent}.\n * @param denseTypeMap - every persisted f_sequence → type, forwarded to\n * {@link rowToEvent} (kept together with `seqMap`).\n * @returns the preserved event prefix, plus `tornFrom` — the persisted seq the\n * physical delete starts at — when a torn tail exists.\n */\nexport function scanRows(\n rows: readonly EventRow[],\n base = 0,\n seqMap?: ReadonlyMap<number, number>,\n denseTypeMap?: ReadonlyMap<number, string>,\n): { preserved: SessionEvent[]; tornFrom?: number } {\n // Pass 1: parse each row's data; a row whose data is not valid JSON is a hole.\n // (The seq/type COLUMNS are always present even when `data` is corrupt.)\n interface Parsed {\n ok: boolean;\n event?: SessionEvent;\n }\n const parsed: Parsed[] = rows.map((row) => {\n try {\n return { ok: true, event: rowToEvent(row, seqMap, denseTypeMap) };\n } catch {\n return { ok: false };\n }\n });\n\n // The last index that is a valid `turn/end` — holes through a closed turn\n // are always committed corruption.\n let lastTurnEnd = -1;\n for (let i = parsed.length - 1; i >= 0; i--) {\n if (parsed[i]?.ok && rows[i]?.fKind === \"turn/end\") {\n lastTurnEnd = i;\n break;\n }\n }\n\n // Preserve the contiguous prefix, including a complete interrupted turn;\n // holes through the last committed boundary throw, while later holes stop.\n const preserved: SessionEvent[] = [];\n for (let i = 0; i < rows.length; i++) {\n const p = parsed[i];\n if (!p?.ok || p.event === undefined) {\n if (i <= lastTurnEnd)\n throw new Error(\n `corrupt session log: unparsable committed event at seq ${rows[i]?.fSequence}`,\n );\n break; // torn tail fragment after the last turn/end — stop, tolerate\n }\n if (p.event.seq !== base + i) {\n if (i <= lastTurnEnd)\n throw new Error(\n `corrupt session log: seq gap in committed region (expected ${base + i}, got ${p.event.seq})`,\n );\n break; // gap after the last turn/end — torn tail, stop\n }\n preserved.push(p.event);\n }\n\n // Any rows past the preserved prefix are a never-committed torn tail; their\n // first seq is the deletion point for load's physical repair.\n return preserved.length < rows.length\n ? { preserved, tornFrom: base + preserved.length }\n : { preserved };\n}\n","/**\n * Per-instance write-authority state for one session-persistence backend.\n *\n * Each `PersistenceCoordinator` instance keeps its own UPSTREAM seq cursor in\n * memory, while this backend persists events under a DENSE seq. Two backend\n * instances (another `dsh` process, or a duplicate persistence plugin in the\n * same process) sharing one database therefore cannot both append to the same\n * session id: the second writer would silently re-number its batch onto the\n * first writer's tail and corrupt the log (event content and seq semantics all\n * misaligned — `UNIQUE(f_session_id, f_sequence)` cannot catch it because\n * dense re-numbering never collides). This guard records, per session, the\n * last CONFIRMED dense head this instance has seen — its own writes or\n * `loadStored` observations — and rejects any append whose on-disk head no\n * longer matches. One writer per session per log; a second writer fails loud\n * instead of corrupting the log. Different session ids are independent (each\n * has its own head), so two instances writing different sessions remain a\n * supported multi-process deployment.\n *\n * The guard also tracks the upstream seqs of events this instance has dropped\n * per session — delta events (`assistant/chunk`) and events the writer marked\n * `ignorable`. A later `assistant/message` may carry `sourceEventSeqs`\n * referencing those events' upstream seqs; the references can never be remapped\n * on read (the referenced events have no persisted row), so the write path\n * prunes them. The concurrent-writer guarantee above limits each session to\n * one writer, making this instance the only authority for its dropped seqs.\n *\n * Pure in-memory state machine — no I/O — so the coordinator's timing contract\n * (never-read vs. confirmed absence vs. confirmed head; confirm after append /\n * after load / re-confirm after repair) is directly unit-testable instead of\n * requiring end-to-end multi-instance setups.\n * @module @morlay/session-rdb/write-guard\n */\n\nimport type { SessionId } from \"@deepseek-ai/dsh-session\";\nimport { pruneSourceEventSeqs } from \"./log.ts\";\n\n/**\n * The write-authority state machine for one backend instance. Not part of the\n * {@link Backend} seam: it guards the orchestration layer's own invariants and\n * lives entirely in memory.\n */\nexport class WriteGuard {\n /**\n * Last CONFIRMED dense head per session — the head this instance itself\n * wrote or observed via `loadStored`. `-1` records a confirmed absence (no\n * row). `undefined` (absent from the map) means this instance never read or\n * wrote the session.\n */\n private readonly headSeqs = new Map<SessionId, number>();\n\n /**\n * Upstream seqs of delta events dropped per session. Mirrors `headSeqs` in\n * shape: the concurrent-writer guarantee limits each session to one writer,\n * so this instance is the only authority for its dropped seqs.\n */\n private readonly filteredSeqs = new Map<SessionId, Set<number>>();\n\n /**\n * Record a head this instance actually observed or wrote.\n * @param id - the session id.\n * @param head - the confirmed dense head, or `-1` for a confirmed absence\n * (a fresh session this instance has read about — a later append to a\n * session that meanwhile got a row must reject).\n */\n confirmHead(id: SessionId, head: number): void {\n this.headSeqs.set(id, head);\n }\n\n /**\n * Fail loud when the on-disk head no longer matches this instance's last\n * confirmed head for the session. `undefined` (never read/written here) is\n * only acceptable for a session with NO row: a row written by someone else\n * means this instance's coordinator cursor is not the log's authority.\n * @param id - the session id.\n * @param storedHead - the on-disk head cursor, read inside the append\n * transaction before any re-numbering happens.\n */\n assertNoConcurrentWriter(id: SessionId, storedHead: number): void {\n const known = this.headSeqs.get(id);\n if (known === undefined) {\n if (storedHead !== -1) {\n throw new Error(\n `session \"${id}\" has a persisted log this instance has not read; another writer may own it — load the session first`,\n );\n }\n return;\n }\n if (known !== storedHead) {\n throw new Error(\n `session \"${id}\" was modified by another writer (stored head ${storedHead}, this instance last confirmed head ${known}); ` +\n \"concurrent writers on one session are not supported\",\n );\n }\n }\n\n /**\n * Record the upstream seqs of events dropped for a session (delta events and\n * ignorable events), so a later batch's `assistant/message` can prune\n * `sourceEventSeqs` references to events that never got a persisted row.\n * @param id - the session id.\n * @param seqs - the dropped events' upstream seqs (pure-delta batches included).\n */\n noteDropped(id: SessionId, seqs: Iterable<number>): void {\n const known = this.filteredSeqs.get(id) ?? new Set<number>();\n for (const seq of seqs) known.add(seq);\n this.filteredSeqs.set(id, known);\n }\n\n /**\n * Prune `sourceEventSeqs` references that hit this session's dropped-delta\n * seq set. `undefined`-like state (no dropped seqs recorded for the session)\n * leaves the list untouched, matching the write path's \"no known drops →\n * keep verbatim\" semantics (repair closers, which never carry provenance,\n * call through the identity path).\n *\n * The predicate is THIS INSTANCE's view (see\n * {@link pruneSourceEventSeqs}): only seqs it knows were dropped are\n * pruned — references to rows persisted by another instance (e.g. a resume\n * seed segment) must survive, so the disk-wide view used by the one-shot\n * repair script is not applicable here.\n * @param id - the session id.\n * @param refs - the event's `sourceEventSeqs` (upstream seqs).\n * @returns the pruned list; identical content when nothing was dropped.\n */\n pruneRefs(id: SessionId, refs: readonly number[]): number[] {\n const dropped = this.filteredSeqs.get(id);\n if (dropped === undefined || dropped.size === 0) return [...refs];\n return pruneSourceEventSeqs(refs, (seq) => !dropped.has(seq));\n }\n}\n","/**\n * 方言无关的表描述类型:`src/entities/*.ts` 以本模块的类型声明每张表一次,\n * SQLite 与 PostgreSQL 的表对象(drizzle `sqliteTable` / `pgTable`)和建表\n * DDL 都由这些描述生成——实体定义是唯一来源,不再手写两套 drizzle 表与裸\n * SQL DDL。\n *\n * @module @morlay/session-rdb/entities/types\n */\n\n/**\n * 列类型(方言无关):\n * - `serial` — 自增主键(sqlite `INTEGER PRIMARY KEY AUTOINCREMENT` /\n * pg `SERIAL PRIMARY KEY`);\n * - `integer` — 32 位整数(sqlite `INTEGER` / pg `INTEGER`);\n * - `bigint` — 64 位整数(sqlite `INTEGER` / pg `BIGINT`,用于毫秒时间戳);\n * - `text` — 文本(sqlite `TEXT` / pg `TEXT`)。\n */\nexport type ColumnTypeName = \"serial\" | \"integer\" | \"bigint\" | \"text\";\n\n/** 外键级联动作(drizzle `UpdateDeleteAction` 的子集)。 */\nexport type DeleteAction = \"cascade\" | \"set null\" | \"restrict\" | \"no action\";\n\n/** 一列的定义(物理列名一律 `f_` 前缀)。 */\nexport interface ColumnDef {\n /** 物理列名(`f_` 前缀)。 */\n name: string;\n type: ColumnTypeName;\n notNull?: boolean;\n /** 列级主键;`serial` 列同时携带自增语义。 */\n primaryKey?: boolean;\n /** 字面量默认值(字符串/数字)。 */\n default?: string | number;\n /** 列级 UNIQUE。 */\n unique?: boolean;\n /** 列级外键(目标表 + 目标列 + 级联动作)。 */\n references?: {\n table: string;\n column: string;\n onDelete?: DeleteAction;\n };\n}\n\n/** 表级 CHECK 约束(expression 为 SQL 片段,如 `f_singleton = 1`)。 */\nexport interface CheckDef {\n name: string;\n expression: string;\n}\n\n/** 表级 UNIQUE 约束。 */\nexport interface UniqueDef {\n name?: string;\n columns: string[];\n}\n\n/** 普通索引(建表后独立 CREATE INDEX)。 */\nexport interface IndexDef {\n name: string;\n columns: string[];\n}\n\n/** 一张表的完整描述。 */\nexport interface TableDef {\n /** 物理表名(`t_` 前缀)。 */\n name: string;\n columns: ColumnDef[];\n checks?: CheckDef[];\n uniques?: UniqueDef[];\n indexes?: IndexDef[];\n}\n\n/** 把物理列名转成 drizzle 表对象的属性名(`f_session_id` → `fSessionId`)。 */\nexport function toProperty(name: string): string {\n return name.replace(/_([a-z])/g, (_match, char: string) => char.toUpperCase());\n}\n","/**\n * 从方言无关的表描述生成 SQLite 的 drizzle `sqliteTable` 对象。查询的类型\n * 安全由调用方的手写行接口(`backend.ts` 的 `SessionRow` / `EventRow` /\n * `EventInsert`)与显式投影兜底——drizzle 无法从运行时构建的列映射保留\n * 精确的列类型。\n *\n * @module @morlay/session-rdb/entities/to-sqlite\n */\n\nimport { sql } from \"drizzle-orm\";\nimport {\n check as sqliteCheck,\n index,\n integer,\n sqliteTable,\n text,\n unique,\n type AnySQLiteColumn,\n type AnySQLiteTable,\n type SQLiteColumnBuilder,\n} from \"drizzle-orm/sqlite-core\";\nimport { toProperty, type ColumnDef, type TableDef } from \"../entities/types.ts\";\n\n/** 构建过程中共享的目标表注册表(供列级 `references` 闭包延迟解析)。 */\ntype TableRegistry = Record<string, AnySQLiteTable>;\n\nfunction buildColumn(c: ColumnDef, tables: TableRegistry): SQLiteColumnBuilder {\n // 具体 builder 类型(SQLiteIntegerBuilder / SQLiteTextBuilder)与基类\n // `SQLiteColumnBuilder` 在方法层面不兼容(泛型逆变),构建阶段用宽松类型\n // 合并,返回时收窄为基类;查询的类型安全由手写行接口兜底。\n let col: any;\n switch (c.type) {\n case \"text\":\n col = text(c.name);\n break;\n case \"serial\":\n col = integer(c.name).primaryKey({ autoIncrement: true });\n break;\n case \"integer\":\n case \"bigint\": {\n const built = integer(c.name);\n col = c.primaryKey ? built.primaryKey() : built;\n break;\n }\n }\n if (c.notNull) col = col.notNull();\n if (c.default !== undefined) col = col.default(c.default);\n if (c.unique) col = col.unique();\n if (c.references) {\n const { table, column, onDelete } = c.references;\n col = col.references(\n () => (tables[table] as unknown as Record<string, AnySQLiteColumn>)[toProperty(column)],\n { onDelete },\n );\n }\n return col as SQLiteColumnBuilder;\n}\n\n/** 由表描述构建 SQLite drizzle 表对象(按传入顺序;外键目标须先构建)。 */\nexport function toSqliteSchema(defs: readonly TableDef[]): Record<string, AnySQLiteTable> {\n const tables: TableRegistry = {};\n for (const def of defs) {\n const columns: Record<string, SQLiteColumnBuilder> = {};\n for (const c of def.columns) columns[toProperty(c.name)] = buildColumn(c, tables);\n const extra = (self: Record<string, unknown>) => [\n ...(def.checks ?? []).map((c) => sqliteCheck(c.name, sql.raw(c.expression))),\n ...(def.uniques ?? []).map((u) =>\n unique(u.name).on(\n ...(u.columns.map((name) => self[toProperty(name)] as AnySQLiteColumn) as [\n AnySQLiteColumn,\n ...AnySQLiteColumn[],\n ]),\n ),\n ),\n ...(def.indexes ?? []).map((i) =>\n index(i.name).on(\n ...(i.columns.map((name) => self[toProperty(name)] as AnySQLiteColumn) as [\n AnySQLiteColumn,\n ...AnySQLiteColumn[],\n ]),\n ),\n ),\n ];\n tables[def.name] = sqliteTable(\n def.name,\n columns as Record<string, SQLiteColumnBuilder>,\n extra as never,\n );\n }\n return tables;\n}\n","/**\n * 从方言无关的表描述生成 PostgreSQL 的 drizzle `pgTable` 对象。查询的类型\n * 安全由调用方的手写行接口与显式投影兜底(同 `to-sqlite.ts`)。\n *\n * @module @morlay/session-rdb/entities/to-postgres\n */\n\nimport { sql } from \"drizzle-orm\";\nimport {\n bigint,\n check as pgCheck,\n index,\n integer,\n pgTable,\n serial,\n text,\n unique,\n type AnyPgColumn,\n type AnyPgColumnBuilder,\n type AnyPgTable,\n} from \"drizzle-orm/pg-core\";\nimport { toProperty, type ColumnDef, type TableDef } from \"../entities/types.ts\";\n\n/** 构建过程中共享的目标表注册表(供列级 `references` 闭包延迟解析)。 */\ntype TableRegistry = Record<string, AnyPgTable>;\n\nfunction buildColumn(c: ColumnDef, tables: TableRegistry): AnyPgColumnBuilder {\n // 具体 builder 类型与 AnyPgColumnBuilder 在方法层面不兼容,构建阶段用宽松类型。\n let col: any;\n switch (c.type) {\n case \"text\":\n col = text(c.name);\n break;\n case \"serial\":\n col = serial(c.name).primaryKey();\n break;\n case \"integer\": {\n const built = integer(c.name);\n col = c.primaryKey ? built.primaryKey() : built;\n break;\n }\n case \"bigint\": {\n const built = bigint(c.name, { mode: \"number\" });\n col = c.primaryKey ? built.primaryKey() : built;\n break;\n }\n }\n if (c.notNull) col = col.notNull();\n if (c.default !== undefined) col = col.default(c.default);\n if (c.unique) col = col.unique();\n if (c.references) {\n const { table, column, onDelete } = c.references;\n col = col.references(\n () => (tables[table] as unknown as Record<string, AnyPgColumn>)[toProperty(column)],\n { onDelete },\n );\n }\n return col;\n}\n\n/** 由表描述构建 PostgreSQL drizzle 表对象(按传入顺序;外键目标须先构建)。 */\nexport function toPostgresSchema(defs: readonly TableDef[]): Record<string, AnyPgTable> {\n const tables: TableRegistry = {};\n for (const def of defs) {\n const columns: Record<string, AnyPgColumnBuilder> = {};\n for (const c of def.columns) columns[toProperty(c.name)] = buildColumn(c, tables);\n const extra = (self: Record<string, unknown>) => [\n ...(def.checks ?? []).map((c) => pgCheck(c.name, sql.raw(c.expression))),\n ...(def.uniques ?? []).map((u) =>\n unique(u.name).on(\n ...(u.columns.map((name) => self[toProperty(name)] as AnyPgColumn) as [\n AnyPgColumn,\n ...AnyPgColumn[],\n ]),\n ),\n ),\n ...(def.indexes ?? []).map((i) =>\n index(i.name).on(\n ...(i.columns.map((name) => self[toProperty(name)] as AnyPgColumn) as [\n AnyPgColumn,\n ...AnyPgColumn[],\n ]),\n ),\n ),\n ];\n tables[def.name] = pgTable(\n def.name,\n columns as Record<string, AnyPgColumnBuilder>,\n extra as never,\n );\n }\n return tables;\n}\n","/**\n * 从方言无关的表描述生成建表 DDL(`CREATE TABLE IF NOT EXISTS` 与独立索引)。\n * SQLite 表附带 `STRICT`,PostgreSQL 用 `SERIAL` / `BIGINT` 等方言类型。\n * 这是实体的唯一 DDL 来源——不手写 SQL 字符串。\n *\n * @module @morlay/session-rdb/entities/ddl\n */\n\nimport type { ColumnDef, TableDef } from \"../entities/types.ts\";\n\nexport type Dialect = \"sqlite\" | \"postgres\";\n\nfunction sqlType(dialect: Dialect, type: ColumnDef[\"type\"]): string {\n switch (type) {\n case \"serial\":\n return dialect === \"sqlite\" ? \"INTEGER\" : \"SERIAL\";\n case \"integer\":\n return \"INTEGER\";\n case \"bigint\":\n return dialect === \"sqlite\" ? \"INTEGER\" : \"BIGINT\";\n case \"text\":\n return \"TEXT\";\n }\n}\n\nfunction literal(value: string | number): string {\n return typeof value === \"string\" ? `'${value.replace(/'/g, \"''\")}'` : String(value);\n}\n\nfunction quote(name: string): string {\n return `\"${name}\"`;\n}\n\nfunction columnSql(dialect: Dialect, c: ColumnDef): string {\n let sql = `${quote(c.name)} ${sqlType(dialect, c.type)}`;\n if (c.primaryKey) sql += \" PRIMARY KEY\";\n if (c.type === \"serial\" && dialect === \"sqlite\") sql += \" AUTOINCREMENT\";\n if (c.notNull) sql += \" NOT NULL\";\n if (c.default !== undefined) sql += ` DEFAULT ${literal(c.default)}`;\n if (c.unique) sql += \" UNIQUE\";\n if (c.references) {\n sql += ` REFERENCES ${quote(c.references.table)}(${quote(c.references.column)})`;\n if (c.references.onDelete) sql += ` ON DELETE ${c.references.onDelete.toUpperCase()}`;\n }\n return sql;\n}\n\n/** 一张表的 `CREATE TABLE IF NOT EXISTS`(表级约束内联在列清单末尾)。 */\nexport function createTableSql(dialect: Dialect, def: TableDef): string {\n const parts = def.columns.map((c) => columnSql(dialect, c));\n for (const ck of def.checks ?? []) parts.push(`CHECK (${ck.expression})`);\n for (const u of def.uniques ?? []) {\n parts.push(`UNIQUE (${u.columns.map(quote).join(\", \")})`);\n }\n const strict = dialect === \"sqlite\" ? \" STRICT\" : \"\";\n return `CREATE TABLE IF NOT EXISTS ${quote(def.name)} (\\n ${parts.join(\",\\n \")}\\n)${strict}`;\n}\n\n/** 一张表的独立索引语句(两方言索引 DDL 相同,无需 dialect 参数)。 */\nexport function createIndexSql(def: TableDef, name: string): string {\n const idx = def.indexes?.find((i) => i.name === name);\n if (idx === undefined) throw new Error(`unknown index \"${name}\" on table \"${def.name}\"`);\n return `CREATE INDEX IF NOT EXISTS ${quote(idx.name)} ON ${quote(def.name)}(${idx.columns\n .map(quote)\n .join(\", \")})`;\n}\n\n/** 一组表的全部建表语句(每表一条 CREATE TABLE + 每条索引)。 */\nexport function createTablesSql(dialect: Dialect, defs: readonly TableDef[]): string[] {\n const statements: string[] = [];\n for (const def of defs) {\n statements.push(createTableSql(dialect, def));\n for (const idx of def.indexes ?? []) statements.push(createIndexSql(def, idx.name));\n }\n return statements;\n}\n","import type { TableDef } from \"./types.ts\";\n\n/**\n * `t_persistence_state` — 单例 store 身份行。`f_singleton` 上的 CHECK 把表\n * 钉死为一行(`f_singleton = 1`)。\n */\nexport const persistenceState: TableDef = {\n name: \"t_persistence_state\",\n columns: [\n { name: \"f_singleton\", type: \"integer\", primaryKey: true },\n { name: \"f_store_id\", type: \"text\", notNull: true },\n ],\n checks: [{ name: \"ck_persistence_state_singleton\", expression: \"f_singleton = 1\" }],\n};\n","import type { TableDef } from \"./types.ts\";\n\n/**\n * `t_schema_meta` — PostgreSQL 的 schema 版本 / 应用身份载体(SQLite 用\n * `PRAGMA user_version` / `application_id`,PG 无等价 pragma)。键值表:\n * `schema_version` 与 `application_id` 两行在首次初始化时写入。\n * 仅 PostgreSQL 后端使用。\n */\nexport const schemaMeta: TableDef = {\n name: \"t_schema_meta\",\n columns: [\n { name: \"f_key\", type: \"text\", primaryKey: true },\n { name: \"f_value\", type: \"text\", notNull: true },\n ],\n};\n","import type { TableDef } from \"./types.ts\";\n\n/**\n * `t_sessions` — 会话元数据(`SessionHeader` 列)+ playpen 风格 head 游标\n * (`f_head_event_id` / `f_head_sequence`,事务内维护,append 时提供 parent\n * 链与下一个 seq)。行的存在即 materialized 信号。\n */\nexport const sessions: TableDef = {\n name: \"t_sessions\",\n columns: [\n { name: \"f_id\", type: \"serial\", primaryKey: true },\n { name: \"f_session_id\", type: \"text\", notNull: true, unique: true },\n { name: \"f_head_event_id\", type: \"text\", notNull: true, default: \"\" },\n { name: \"f_head_sequence\", type: \"integer\", notNull: true, default: -1 },\n { name: \"f_version\", type: \"integer\", notNull: true },\n { name: \"f_created_at\", type: \"bigint\", notNull: true },\n { name: \"f_cwd\", type: \"text\" },\n { name: \"f_parent_session\", type: \"text\" },\n { name: \"f_seed_length\", type: \"integer\" },\n { name: \"f_origin\", type: \"text\" },\n { name: \"f_delegation_depth\", type: \"integer\" },\n { name: \"f_incarnation\", type: \"text\", notNull: true },\n { name: \"f_revision\", type: \"integer\", notNull: true },\n ],\n};\n","import type { TableDef } from \"./types.ts\";\n\n/**\n * `t_events` — 全局可寻址的持久化事件实体:`f_event_id`(UUID 唯一)、\n * `f_parent_id`(事件链,空串为 root)、`f_kind`(= 上游 `type`)、\n * `f_role` / `f_name` / `f_action_id`(playpen 事件维度)、`f_encoding`\n * (`json`)、`f_data`(JSON 文本)、`f_created_at`(= `time`)、\n * `f_original_seq`(上游 seq)以及 surface 元数据列(JSON 文本或 NULL)。\n */\nexport const events: TableDef = {\n name: \"t_events\",\n columns: [\n { name: \"f_id\", type: \"serial\", primaryKey: true },\n { name: \"f_event_id\", type: \"text\", notNull: true, unique: true },\n { name: \"f_parent_id\", type: \"text\", notNull: true, default: \"\" },\n { name: \"f_kind\", type: \"text\", notNull: true, default: \"\" },\n { name: \"f_role\", type: \"text\", notNull: true, default: \"\" },\n { name: \"f_name\", type: \"text\", notNull: true, default: \"\" },\n { name: \"f_action_id\", type: \"text\", notNull: true, default: \"\" },\n { name: \"f_encoding\", type: \"text\", notNull: true, default: \"\" },\n { name: \"f_data\", type: \"text\", notNull: true },\n { name: \"f_created_at\", type: \"bigint\", notNull: true, default: 0 },\n { name: \"f_original_seq\", type: \"integer\", notNull: true },\n { name: \"f_source_event_seqs\", type: \"text\" },\n { name: \"f_surface_op\", type: \"text\" },\n ],\n // 无独立索引:查询只经 `f_event_id`(列级 UNIQUE 自动建唯一索引,join 查找侧)\n // 与 `t_session_events` 的复合索引(按 session 过滤后回表取本表列)。事件链\n // `f_parent_id` 仅在写路径构造(读时不回读该列),无按 kind/role/created_at\n // 的查询——不再为不可达查询维护索引(写放大)。\n};\n","import type { TableDef } from \"./types.ts\";\n\n/**\n * `t_session_events` — 会话↔事件桥接表。`(f_session_id, f_sequence)` 唯一且\n * 有序,会话 log 按稠密 seq 读取;删除 torn tail 只删桥接行(事件实体作为\n * 全局行保留)。\n */\nexport const sessionEvents: TableDef = {\n name: \"t_session_events\",\n columns: [\n { name: \"f_id\", type: \"serial\", primaryKey: true },\n {\n name: \"f_session_id\",\n type: \"text\",\n notNull: true,\n references: { table: \"t_sessions\", column: \"f_session_id\", onDelete: \"cascade\" },\n },\n {\n name: \"f_event_id\",\n type: \"text\",\n notNull: true,\n references: { table: \"t_events\", column: \"f_event_id\", onDelete: \"cascade\" },\n },\n { name: \"f_sequence\", type: \"integer\", notNull: true },\n ],\n uniques: [\n { name: \"uq_session_events_session_sequence\", columns: [\"f_session_id\", \"f_sequence\"] },\n ],\n // 不另建普通索引:`UNIQUE(f_session_id, f_sequence)` 约束自动创建的唯一索引\n // 已覆盖本表的全部访问模式(按 session 过滤 + 按 seq 范围/排序/取尾)。\n};\n","/**\n * 实体纯定义:每张表一个文件,方言无关的表描述(列 / 约束 / 索引 / 外键)。\n * 具体后端的 drizzle 表对象与建表 DDL 由 `src/adapters/` 从这里转化生成。\n *\n * @module @morlay/session-rdb/entities\n */\n\nimport { persistenceState } from \"./persistence-state.ts\";\nimport { schemaMeta } from \"./schema-meta.ts\";\nimport { sessions } from \"./sessions.ts\";\nimport { events } from \"./events.ts\";\nimport { sessionEvents } from \"./session-events.ts\";\n\nexport { persistenceState };\nexport { schemaMeta };\nexport { sessions };\nexport { events };\nexport { sessionEvents };\nexport type {\n ColumnDef,\n TableDef,\n CheckDef,\n UniqueDef,\n IndexDef,\n ColumnTypeName,\n DeleteAction,\n} from \"./types.ts\";\n\n/** SQLite 后端使用的表(不含 pg 专用的 `t_schema_meta`)。 */\nexport const sqliteTableDefs = [persistenceState, sessions, events, sessionEvents] as const;\n\n/** PostgreSQL 后端使用的表。 */\nexport const postgresTableDefs = [\n persistenceState,\n schemaMeta,\n sessions,\n events,\n sessionEvents,\n] as const;\n","/**\n * Schema + event classification for the RDB session-persistence backend.\n *\n * The table layout follows the playpen-session store (three-table split):\n * `t_sessions` holds the header plus the head cursor, `t_events` holds each\n * event as a GLOBALLY addressable entity (event id + parent chain +\n * kind/role/name/action-id dimensions), and `t_session_events` bridges\n * sessions to events in per-session seq order.\n *\n * Naming is uniform: every table carries the `t_` prefix and every column the\n * `f_` prefix. The entities are declared ONCE in `src/entities/` (dialect-free\n * table descriptions); this module derives the SQLite drizzle tables from\n * them (STRICT + version/identity pragmas live with the SQLite backend in\n * `sqlite.ts`). There is no migration toolchain and no hand-written DDL.\n *\n * Delta content is NOT persisted: `assistant/chunk` events are dropped at\n * write time, and surviving events are re-numbered to a dense persisted seq\n * (`f_original_seq` keeps the upstream seq, so `sourceEventSeqs` provenance\n * can be remapped on read — see `log.ts`). This gives the backend the same\n * \"ephemeral chunks stay out of the canonical log\" semantics the persistence\n * proposal records, without requiring the upstream session layer to skip seqs.\n *\n * @module @morlay/session-rdb/schema\n */\n\nimport type { SessionEvent } from \"@deepseek-ai/dsh-session\";\nimport { toSqliteSchema } from \"./adapters/index.ts\";\nimport { sqliteTableDefs } from \"./entities/index.ts\";\n\n/**\n * The on-disk schema version. Bumped only on a breaking change to the table\n * layout; orthogonal to a session's own `version` (which versions the EVENT\n * vocabulary, stored per session in the `t_sessions` row).\n */\nexport const SCHEMA_VERSION = 1;\n\n/** SQLite application id protecting unrelated databases from persistence writes. */\nexport const SESSION_PERSISTENCE_SQLITE_APPLICATION_ID = 0x44534850;\n\n/**\n * Event types whose CONTENT is not persisted: the backend drops these rows\n * entirely and re-numbers the surviving events to a dense persisted seq.\n * Mirrors the persistence proposal's \"ephemeral events never enter the\n * canonical log\" split.\n */\nexport const EPHEMERAL_EVENT_TYPES = [\"assistant/chunk\"] as const;\n\n/** `t_events.f_encoding` value: JSON text. Future compression would switch this per row. */\nexport const EVENT_ENCODING = \"json\";\n\n/**\n * SQLite drizzle tables derived from the single entity definitions in\n * `src/entities/`. The runtime column objects are authoritative; TS type\n * safety of queries is carried by the hand-written row interfaces in\n * `backend.ts` (drizzle cannot infer precise column types from a runtime-built\n * column map, so the table handles are deliberately loose).\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nconst sqliteTables: Record<string, any> = toSqliteSchema(sqliteTableDefs);\n\n/** `t_persistence_state` — the singleton row holding the store identity. */\nexport const tPersistenceState = sqliteTables[\"t_persistence_state\"]!;\n\n/** `t_sessions` — the out-of-log metadata plus the playpen-style head cursor. */\nexport const tSessions = sqliteTables[\"t_sessions\"]!;\n\n/** `t_events` — the globally addressable persisted event entity. */\nexport const tEvents = sqliteTables[\"t_events\"]!;\n\n/** `t_session_events` — the session↔event bridge. */\nexport const tSessionEvents = sqliteTables[\"t_session_events\"]!;\n\n/**\n * A row of the `t_sessions` table — the out-of-log metadata ({@link SessionHeader})\n * plus the playpen-style head cursor (the last committed event id and seq). The\n * row's EXISTENCE is the materialization signal: it is written only by the\n * first non-empty append (lazy materialization), so a created-but-never-appended\n * session has no row and is absent from `list`.\n *\n * The canonical shared shape lives in `backend.ts` (dialect-neutral); the\n * drizzle-derived select model is structurally compatible.\n */\nexport type { SessionRow } from \"./backend.ts\";\n\n/** @see {@link import(\"./backend.ts\").EventRow} — shared with the PostgreSQL backend. */\nexport type { EventRow } from \"./backend.ts\";\n\n/**\n * Journal modes the backend will run under. `wal` is the default and the\n * durability model the persistence ADR records; the rollback-journal modes\n * (`delete`/`truncate`/`persist`) exist for filesystems where WAL's\n * shared-memory files do not work (network mounts). `memory`/`off` are\n * excluded: dropping journal durability silently contradicts what this\n * backend promises.\n */\nexport type JournalMode = \"wal\" | \"delete\" | \"truncate\" | \"persist\";\n\n/**\n * How long a connection waits for a contended write lock before failing with\n * `SQLITE_BUSY`. SQLite's default is 0 (fail immediately): with two processes\n * sharing one database (a second `dsh` instance on the same `sessions.sqlite`),\n * every append that meets an in-flight commit would fail and that process's\n * session would silently lose its tail. A nonzero wait makes the contention\n * window a queue instead of a loss.\n */\nexport const DEFAULT_BUSY_TIMEOUT_MS = 5000;\n\n/**\n * Whether an event type is ephemeral (its content must not be persisted).\n * @param type - the upstream `SessionEvent.type`.\n * @returns true for delta events the backend drops at write time.\n */\nexport function isEphemeralType(type: string): boolean {\n return (EPHEMERAL_EVENT_TYPES as readonly string[]).includes(type);\n}\n\n/**\n * Whether an event must be persisted. An event is dropped at write time when\n * its type is ephemeral (content not persisted) OR the writer marked it\n * `ignorable` — the envelope contract promises loss of an ignorable event\n * cannot affect reconstruction, so it never enters the canonical log (the\n * upstream seq is still recorded for provenance pruning, exactly like a\n * dropped delta). `session-branch/version` is a branch-layer lineage fact\n * carried as an ignorable seed event: it stays in the LIVE log but is NOT\n * persisted here — the branch provider persists it in its own version table\n * (`t_branch_versions`), keeping canonical-log semantics intact.\n */\nexport function isPersistedEvent(event: SessionEvent): boolean {\n return !isEphemeralType(event.type) && event.ignorable !== true;\n}\n\n/**\n * Map a persisted event onto the playpen event dimensions. `f_kind` is the\n * upstream type; `f_role`/`f_name`/`f_action_id` are the playpen classification\n * columns. Unknown (plugin-merged) event types keep the playpen defaults so a\n * future extension can classify them without a schema change.\n * @param event - the event to classify (never an ephemeral type at write time).\n * @returns the role, name, and action-id column values.\n */\nexport function eventDimensions(event: SessionEvent): {\n role: string;\n name: string;\n actionId: string;\n} {\n switch (event.type) {\n case \"turn/start\":\n case \"turn/end\":\n case \"step/start\":\n case \"step/end\":\n case \"session/end-seed\":\n return { role: \"turn\", name: \"\", actionId: \"\" };\n case \"user/message\":\n case \"request/header\":\n case \"request/context\":\n return { role: \"user\", name: \"\", actionId: \"\" };\n case \"assistant/message\":\n return { role: \"model\", name: \"\", actionId: \"\" };\n case \"tool/call\":\n return { role: \"function\", name: event.data.name, actionId: event.data.callId };\n case \"tool/result\": {\n // Optional chain: the coordinator migrates pre-identity legacy events\n // only on READ; an append may still carry the old shape without `message`.\n const block = event.data.message?.content[0];\n return { role: \"function\", name: \"\", actionId: block?.toolCallId ?? \"\" };\n }\n case \"todo/write\":\n return { role: \"state\", name: \"todos\", actionId: \"\" };\n default:\n return { role: \"\", name: \"\", actionId: \"\" };\n }\n}\n","/**\n * SQLite 存储后端:在 `node:sqlite` `DatabaseSync` 之上实现 {@link Backend}。\n * drizzle 经 `drizzle-orm/node-sqlite` 驱动包装,查询语义与 PostgreSQL 后端\n * 共用同一套 {@link BackendTx} 原语。数据库打开/建表/schema 版本与身份校验\n * ({@link openDatabase})也归本模块所有——`SqliteBackend` 的实现不跨文件。\n *\n * 事务:SQLite 单连接,`BEGIN IMMEDIATE` 提前获取写锁(受 `busy_timeout`\n * pragma 排队保护);`COMMIT`/`ROLLBACK` 之后同一连接继续服务普通查询。\n * @module @morlay/session-rdb/sqlite\n */\n\nimport { randomUUID } from \"node:crypto\";\nimport { statSync } from \"node:fs\";\nimport { mkdir, open } from \"node:fs/promises\";\nimport { dirname, resolve } from \"node:path\";\nimport { DatabaseSync } from \"node:sqlite\";\nimport { and, desc, eq, gte, inArray, sql } from \"drizzle-orm\";\nimport { drizzle, type NodeSQLiteDatabase } from \"drizzle-orm/node-sqlite\";\nimport type { SessionHeader, SessionId } from \"@deepseek-ai/dsh-session\";\nimport {\n type Backend,\n type BackendTx,\n type EventInsert,\n type EventRow,\n type SessionRow,\n} from \"./backend.ts\";\nimport { createTablesSql } from \"./adapters/index.ts\";\nimport { sqliteTableDefs } from \"./entities/index.ts\";\nimport { sessionConflictRow, sessionInsertRow } from \"./log.ts\";\nimport {\n DEFAULT_BUSY_TIMEOUT_MS,\n SCHEMA_VERSION,\n SESSION_PERSISTENCE_SQLITE_APPLICATION_ID,\n tEvents,\n tPersistenceState,\n tSessionEvents,\n tSessions,\n type JournalMode,\n} from \"./schema.ts\";\n\n/** The drizzle handle over the open `node:sqlite` database, plus its raw client. */\ntype SqliteDb = NodeSQLiteDatabase & { $client: DatabaseSync };\n\n/**\n * Process-wide SQLite write-transaction queues, keyed by database path.\n * SQLite allows exactly one writer, and the async transaction callback (see\n * {@link SqliteBackend.transaction}) must never overlap another connection's\n * `BEGIN IMMEDIATE` inside this process: the second, synchronous BEGIN would\n * busy-wait on the lock and freeze the event loop, so the lock holder could\n * never commit (deadlock until busy_timeout). Serializing per PATH removes the\n * gap entirely.\n *\n * The queue is per path, not global: two backends on DIFFERENT files have no\n * lock to contend for, so serializing them would be pure waste. Two instances\n * sharing one file (the supported multi-process deployment) still share one\n * queue, preserving the deadlock guarantee. `:memory:` databases are distinct\n * per connection but share the key — serializing them is harmless (tests).\n */\nconst sqliteTxQueues = new Map<string, Promise<void>>();\n\n/** Run `fn` behind the write-transaction queue for one database path. */\nfunction enqueueSqliteTx<T>(path: string, fn: () => Promise<T>): Promise<T> {\n const tail = sqliteTxQueues.get(path) ?? Promise.resolve();\n const run = tail.then(fn);\n // A failed transaction must not poison the queue for later ones.\n sqliteTxQueues.set(\n path,\n run.then(\n () => undefined,\n () => undefined,\n ),\n );\n return run;\n}\n\n/**\n * Exclusively create a missing database file with owner-only permissions.\n * Existing files retain their modes, and errors other than `EEXIST` propagate.\n * `DatabaseSync` reopens by path, so this does not protect confidentiality or\n * integrity when another principal can replace the database entry in its parent\n * directory.\n */\nasync function createDatabaseFile(path: string): Promise<void> {\n try {\n const handle = await open(path, \"wx\", 0o600);\n await handle.close();\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code !== \"EEXIST\") throw error;\n }\n}\n\n/**\n * Open the database and apply its schema and pragmas. An empty database with a\n * zero `user_version` is initialized at {@link SCHEMA_VERSION}; a nonempty\n * unversioned database and every other non-current version reject rather than\n * being migrated in place.\n * @param path - the SQLite database file to open (created when absent).\n * @param journalMode - validated journal pragma.\n * @param busyTimeout - milliseconds to wait for a contended write lock before\n * failing with `SQLITE_BUSY`; `0` fails immediately (SQLite's default).\n * @returns the open handle with pragmas applied and all tables ensured.\n */\nexport function openDatabase(\n path: string,\n journalMode: JournalMode,\n busyTimeout = DEFAULT_BUSY_TIMEOUT_MS,\n): DatabaseSync {\n const db = new DatabaseSync(path);\n try {\n configureDatabase(db, path, journalMode, busyTimeout);\n return db;\n } catch (error: unknown) {\n db.close();\n throw error;\n }\n}\n\nfunction configureDatabase(\n db: DatabaseSync,\n path: string,\n journalMode: JournalMode,\n busyTimeout: number,\n): void {\n // The remaining raw statements are driver-level SQLite operations with no\n // drizzle API: connection pragmas (foreign_keys / busy_timeout / journal_mode\n // / user_version / application_id) and the sqlite_schema system-table probe.\n db.exec(\"PRAGMA foreign_keys = ON\");\n // The busy timeout must precede every lock acquisition (the initialization\n // transaction below, and each write transaction in the backend) so a\n // contended database queues instead of failing one process's writes outright.\n db.exec(`PRAGMA busy_timeout = ${busyTimeout}`);\n const dbx = drizzle({ client: db });\n // Initialization runs in ONE drizzle transaction (`BEGIN IMMEDIATE` acquires\n // the write lock up front). Validate while holding the write lock so no other\n // connection can change schema ownership between inspection and init.\n dbx.transaction(\n (tx) => {\n const { user_version: onDisk } = tx.get(sql`PRAGMA user_version`) as {\n user_version: number;\n };\n const { application_id: applicationId } = tx.get(sql`PRAGMA application_id`) as {\n application_id: number;\n };\n const { count: userObjectCount } = tx.get(\n sql`SELECT COUNT(*) AS count FROM sqlite_schema WHERE name NOT GLOB 'sqlite_*'`,\n ) as { count: number };\n if (onDisk === 0 && (applicationId !== 0 || userObjectCount > 0)) {\n throw new Error(\n `session database at \"${path}\" has an unversioned schema or application identity`,\n );\n }\n if (onDisk !== 0 && onDisk !== SCHEMA_VERSION) {\n throw new Error(\n `session database at \"${path}\" has schema version ${onDisk}, incompatible with this build (${SCHEMA_VERSION})`,\n );\n }\n if (\n onDisk === SCHEMA_VERSION &&\n applicationId !== SESSION_PERSISTENCE_SQLITE_APPLICATION_ID\n ) {\n throw new Error(\n `session database at \"${path}\" has application id ${applicationId}, expected ${SESSION_PERSISTENCE_SQLITE_APPLICATION_ID}`,\n );\n }\n for (const statement of createTablesSql(\"sqlite\", sqliteTableDefs)) {\n tx.run(sql.raw(statement));\n }\n // The store identity row comes from the same drizzle table object; the\n // OR-IGNORE semantics are drizzle's `onConflictDoNothing` (the singleton\n // row already exists after the first open).\n tx.insert(tPersistenceState)\n .values({ fSingleton: 1, fStoreId: randomUUID() })\n .onConflictDoNothing()\n .run();\n if (onDisk === 0) {\n tx.run(sql.raw(`PRAGMA application_id = ${SESSION_PERSISTENCE_SQLITE_APPLICATION_ID}`));\n tx.run(sql.raw(`PRAGMA user_version = ${SCHEMA_VERSION}`));\n }\n },\n { behavior: \"immediate\" },\n );\n // The validated union is safe to interpolate into a non-bindable PRAGMA.\n // Apply it only after ownership validation and initialization commit.\n db.exec(`PRAGMA journal_mode = ${journalMode.toUpperCase()}`);\n}\n\n/** SQLite 后端的打开参数(来自配置的 sqlite 分支)。 */\nexport interface SqliteBackendOptions {\n path: string;\n journalMode: JournalMode;\n busyTimeout: number;\n}\n\n/**\n * SQLite 存储后端。打开时创建目录/文件(owner-only)、应用 pragma 与 DDL、\n * 校验 schema 版本/应用身份并读取 store id。\n */\nexport class SqliteBackend implements Backend {\n readonly kind = \"sqlite\" as const;\n storeIdentity!: string;\n\n /** The resolved database path (queue key); set by {@link open}. */\n private dbPath = \"\";\n private db!: SqliteDb;\n\n constructor(private readonly options: SqliteBackendOptions) {}\n\n async open(): Promise<void> {\n const actual =\n this.options.path === \":memory:\" ? this.options.path : resolve(this.options.path);\n this.dbPath = actual;\n if (actual !== \":memory:\") {\n await mkdir(dirname(actual), { recursive: true, mode: 0o700 });\n await createDatabaseFile(actual);\n }\n // The initialization transaction inside openDatabase is a synchronous\n // `BEGIN IMMEDIATE`, so it must queue behind the same per-path write queue\n // as the backend's own transactions: if another instance (sharing the\n // file) is mid-transaction and its async callback is yielding, a\n // synchronous BEGIN here would busy-wait and freeze the event loop until\n // busy_timeout — the other instance could never commit. Queuing turns that\n // \"open collides with an in-flight write\" race into a queue wait.\n await enqueueSqliteTx(actual, async () => {\n this.db = drizzle({\n client: openDatabase(actual, this.options.journalMode, this.options.busyTimeout),\n });\n });\n try {\n const row = this.db\n .select({ fStoreId: tPersistenceState.fStoreId })\n .from(tPersistenceState)\n .where(eq(tPersistenceState.fSingleton, 1))\n .get() as { fStoreId: string } | undefined;\n /* v8 ignore next -- openDatabase inserts the singleton before returning. */\n if (row === undefined) {\n throw new Error(`session database at \"${actual}\" has no store identity`);\n }\n if (row.fStoreId.length === 0) {\n throw new Error(`session database at \"${actual}\" has no valid store identity`);\n }\n if (actual !== \":memory:\") {\n const identity = statSync(actual, { bigint: true });\n this.storeIdentity = `file:${identity.dev}:${identity.ino}:${identity.birthtimeNs}:store:${row.fStoreId}`;\n } else {\n this.storeIdentity = `memory:store:${row.fStoreId}`;\n }\n } catch (error: unknown) {\n this.db.$client.close();\n throw error;\n }\n }\n\n async close(): Promise<void> {\n // `open` may have failed before assigning `db` (e.g. the queued init threw);\n // close must not crash the coordinator's dispose on top of that failure.\n if (this.db === undefined) return;\n this.db.$client.close();\n }\n\n async getSession(id: SessionId): Promise<SessionRow | undefined> {\n return this.db.select().from(tSessions).where(eq(tSessions.fSessionId, id)).get() as\n | SessionRow\n | undefined;\n }\n\n async getSeqMapRows(\n id: SessionId,\n ): Promise<Array<{ fSequence: number; fOriginalSeq: number; fKind: string }>> {\n return this.eventRows().where(eq(tSessionEvents.fSessionId, id)).all();\n }\n\n async getEventRows(id: SessionId, fromSequence?: number): Promise<EventRow[]> {\n const scoped =\n fromSequence === undefined\n ? this.eventRows().where(eq(tSessionEvents.fSessionId, id))\n : this.eventRows().where(\n and(eq(tSessionEvents.fSessionId, id), gte(tSessionEvents.fSequence, fromSequence)),\n );\n return scoped.orderBy(tSessionEvents.fSequence).all() as unknown as EventRow[];\n }\n\n async listSessions(): Promise<SessionRow[]> {\n return this.db.select().from(tSessions).all() as SessionRow[];\n }\n\n async transaction<T>(fn: (tx: BackendTx) => Promise<T>): Promise<T> {\n // drizzle's SQLite driver only supports SYNCHRONOUS transaction callbacks\n // (an async callback is rejected at the type level), while the shared\n // `BackendTx` interface is async because PostgreSQL is. The BEGIN/COMMIT/\n // ROLLBACK statements below are therefore driver-level and unavoidable.\n //\n // The async transaction callback also yields microtask gaps while the write\n // lock is held. A second connection in THIS process (another backend\n // instance sharing the file) that synchronously executes `BEGIN IMMEDIATE`\n // during such a gap would busy-wait on the lock and block the event loop,\n // so the lock holder could never commit (deadlock until busy_timeout).\n // Serializing the per-path write queue (see {@link enqueueSqliteTx})\n // removes the gap entirely — SQLite has one writer anyway, and a separate\n // process has its own event loop, so cross-process contention still\n // resolves through busy_timeout. A backend on a DIFFERENT database file has\n // no lock to contend for and is not serialized with this one.\n return enqueueSqliteTx(this.dbPath, async () => {\n this.db.$client.exec(\"BEGIN IMMEDIATE\");\n try {\n const result = await fn(this.tx);\n this.db.$client.exec(\"COMMIT\");\n return result;\n } catch (error: unknown) {\n // The DELETE+INSERT cannot collide; this rolls back a DB-level failure\n // (disk full, etc.), unreachable in test.\n /* v8 ignore start */\n try {\n this.db.$client.exec(\"ROLLBACK\");\n } catch {\n // The original SQLite failure remains the actionable cause.\n }\n throw error;\n /* v8 ignore stop */\n }\n });\n }\n\n /**\n * SQLite is a single connection: after `BEGIN IMMEDIATE` every query on the\n * same handle is inside the transaction, so the tx primitives are the same\n * row primitives used by the non-transactional reads.\n */\n private readonly tx: BackendTx = {\n upsertSession: (meta, incarnation) => this.upsertSession(meta, incarnation),\n getHead: (id) => this.getHead(id),\n insertEvents: (events) => this.insertEvents(events),\n insertBridges: (rows) => this.insertBridges(rows),\n updateHead: (id, headEventId, headSequence) => this.updateHead(id, headEventId, headSequence),\n bumpRevision: (id) => this.bumpRevision(id),\n deleteBridgeTail: (id, fromSequence) => this.deleteBridgeTail(id, fromSequence),\n getPrevBridge: (id, sequence) => this.getPrevBridge(id, sequence),\n getLastBridge: (id) => this.getLastBridge(id),\n updateEventFields: (id, sequence, fields) => this.updateEventFields(id, sequence, fields),\n };\n\n // --- row primitives (transaction-internal or standalone) ---\n\n private async upsertSession(meta: SessionHeader, incarnation: string): Promise<void> {\n this.db\n .insert(tSessions)\n .values(sessionInsertRow(meta, incarnation))\n .onConflictDoUpdate({\n target: tSessions.fSessionId,\n set: sessionConflictRow(meta),\n })\n .run();\n }\n\n private async getHead(\n id: SessionId,\n ): Promise<Pick<SessionRow, \"fHeadEventId\" | \"fHeadSequence\">> {\n const head = this.db\n .select({ fHeadEventId: tSessions.fHeadEventId, fHeadSequence: tSessions.fHeadSequence })\n .from(tSessions)\n .where(eq(tSessions.fSessionId, id))\n .get() as Pick<SessionRow, \"fHeadEventId\" | \"fHeadSequence\"> | undefined;\n /* v8 ignore next -- appendBatch/commitRepair always materialize the row before reading the head */\n if (head === undefined) throw new Error(`session \"${id}\" has no materialized row`);\n return head;\n }\n\n private async insertEvents(events: EventInsert[]): Promise<void> {\n if (events.length === 0) return;\n this.db\n .insert(tEvents)\n .values(events.map((event) => ({ ...event })))\n .run();\n }\n\n private async insertBridges(\n rows: Array<{ fSessionId: SessionId; fEventId: string; fSequence: number }>,\n ): Promise<void> {\n if (rows.length === 0) return;\n this.db\n .insert(tSessionEvents)\n .values(rows.map((row) => ({ ...row })))\n .run();\n }\n\n private async updateHead(\n id: SessionId,\n headEventId: string,\n headSequence: number,\n ): Promise<void> {\n this.db\n .update(tSessions)\n .set({ fHeadEventId: headEventId, fHeadSequence: headSequence })\n .where(eq(tSessions.fSessionId, id))\n .run();\n }\n\n private async bumpRevision(id: SessionId): Promise<void> {\n this.db\n .update(tSessions)\n .set({ fRevision: sql`${tSessions.fRevision} + 1` })\n .where(eq(tSessions.fSessionId, id))\n .run();\n }\n\n private async deleteBridgeTail(id: SessionId, fromSequence: number): Promise<void> {\n this.db\n .delete(tSessionEvents)\n .where(and(eq(tSessionEvents.fSessionId, id), gte(tSessionEvents.fSequence, fromSequence)))\n .run();\n }\n\n private async getPrevBridge(\n id: SessionId,\n sequence: number,\n ): Promise<{ fEventId: string; fSequence: number } | undefined> {\n return this.db\n .select({ fEventId: tSessionEvents.fEventId, fSequence: tSessionEvents.fSequence })\n .from(tSessionEvents)\n .where(and(eq(tSessionEvents.fSessionId, id), eq(tSessionEvents.fSequence, sequence)))\n .get() as { fEventId: string; fSequence: number } | undefined;\n }\n\n private async getLastBridge(\n id: SessionId,\n ): Promise<{ fEventId: string; fSequence: number } | undefined> {\n return this.db\n .select({ fEventId: tSessionEvents.fEventId, fSequence: tSessionEvents.fSequence })\n .from(tSessionEvents)\n .where(eq(tSessionEvents.fSessionId, id))\n .orderBy(desc(tSessionEvents.fSequence))\n .limit(1)\n .get() as { fEventId: string; fSequence: number } | undefined;\n }\n\n /** The joined event-row projection shared by whole-log and suffix reads. */\n private eventRows() {\n return this.db\n .select({\n fSequence: tSessionEvents.fSequence,\n fOriginalSeq: tEvents.fOriginalSeq,\n fKind: tEvents.fKind,\n fCreatedAt: tEvents.fCreatedAt,\n fData: tEvents.fData,\n fSourceEventSeqs: tEvents.fSourceEventSeqs,\n fSurfaceOp: tEvents.fSurfaceOp,\n })\n .from(tSessionEvents)\n .innerJoin(tEvents, eq(tSessionEvents.fEventId, tEvents.fEventId));\n }\n\n private async updateEventFields(\n id: SessionId,\n sequence: number,\n fields: {\n fSourceEventSeqs?: string | null;\n fSurfaceOp?: string | null;\n fData?: string;\n },\n ): Promise<void> {\n const eventIds = this.db\n .select({ fEventId: tSessionEvents.fEventId })\n .from(tSessionEvents)\n .where(and(eq(tSessionEvents.fSessionId, id), eq(tSessionEvents.fSequence, sequence)));\n this.db\n .update(tEvents)\n .set({\n ...(fields.fSourceEventSeqs === undefined\n ? {}\n : { fSourceEventSeqs: fields.fSourceEventSeqs }),\n ...(fields.fSurfaceOp === undefined ? {} : { fSurfaceOp: fields.fSurfaceOp }),\n ...(fields.fData === undefined ? {} : { fData: fields.fData }),\n })\n .where(inArray(tEvents.fEventId, eventIds))\n .run();\n }\n}\n","/**\n * PostgreSQL 存储后端:在 drizzle 的 PG async 驱动之上实现 {@link Backend}。\n * 驱动(`drizzle-orm/node-postgres` 生产 / `drizzle-orm/pglite` 测试)在构造\n * 时注入,因此本模块不依赖具体 PG 客户端包。\n *\n * 与 SQLite 的差异(方言事实,非行为差异):\n * - `f_created_at` 用 `BIGINT`(毫秒时间戳超出 PG `INTEGER` 的 int32 范围);\n * - schema 版本/应用身份校验用 `t_schema_meta` 键值表代替 SQLite 的\n * `PRAGMA user_version` / `application_id`(PG 无等价 pragma);\n * - 事务用 drizzle 的异步 `db.transaction`(PG 无 `BEGIN IMMEDIATE`,写锁靠\n * `busy_timeout` 之外的数据库行锁/唯一约束兜底)。\n * @module @morlay/session-rdb/postgres\n */\n\nimport { randomUUID } from \"node:crypto\";\nimport { and, desc, eq, gte, inArray, sql } from \"drizzle-orm\";\nimport type { PgAsyncDatabase, PgAsyncTransaction, PgQueryResultHKT } from \"drizzle-orm/pg-core\";\nimport type { SessionHeader, SessionId } from \"@deepseek-ai/dsh-session\";\nimport {\n type Backend,\n type BackendTx,\n type EventInsert,\n type EventRow,\n type SessionRow,\n} from \"./backend.ts\";\nimport { SCHEMA_VERSION, SESSION_PERSISTENCE_SQLITE_APPLICATION_ID } from \"./schema.ts\";\nimport { createTablesSql, toPostgresSchema } from \"./adapters/index.ts\";\nimport { postgresTableDefs } from \"./entities/index.ts\";\nimport { sessionConflictRow, sessionInsertRow } from \"./log.ts\";\n\n/**\n * PostgreSQL drizzle tables derived from the single entity definitions in\n * `src/entities/` (type safety carried by the hand-written row interfaces in\n * `backend.ts`, same as the SQLite side).\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nconst pgTables: Record<string, any> = toPostgresSchema(postgresTableDefs);\n\n/** `t_persistence_state` — the singleton row holding the store identity. */\nexport const pgPersistenceState = pgTables[\"t_persistence_state\"];\n\n/** `t_schema_meta` — PG's schema-version / application-identity key-value store. */\nexport const pgSchemaMeta = pgTables[\"t_schema_meta\"];\n\n/** `t_sessions` — the out-of-log metadata plus the playpen-style head cursor. */\nexport const pgSessions = pgTables[\"t_sessions\"];\n\n/** `t_events` — the globally addressable persisted event entity. */\nexport const pgEvents = pgTables[\"t_events\"];\n\n/** `t_session_events` — the session↔event bridge. */\nexport const pgSessionEvents = pgTables[\"t_session_events\"];\n\n/** PostgreSQL 后端的打开参数。 */\nexport interface PostgresBackendOptions {\n /** Revision 源限定前缀(不含 store id),如 `postgres:host:port:database`。 */\n identityBase: string;\n /** 关闭底层连接(生产 `pool.end()`;测试 `pglite.close()`)。 */\n close: () => Promise<void>;\n}\n\n/**\n * PostgreSQL 存储后端。构造时注入 drizzle PG 实例;{@link open} 建表并做\n * schema 版本/应用身份校验(`t_schema_meta`,替代 SQLite 的 PRAGMA)。\n */\nexport class PostgresBackend<THKT extends PgQueryResultHKT = PgQueryResultHKT> implements Backend {\n readonly kind = \"postgres\" as const;\n storeIdentity!: string;\n\n constructor(\n private readonly db: PgAsyncDatabase<THKT>,\n private readonly options: PostgresBackendOptions,\n ) {}\n\n async open(): Promise<void> {\n const storeId = await this.db.transaction(async (tx) => {\n // 探测必须在建表之前:t_schema_meta 存在与否区分「全新库」与「已有库」。\n // `to_regclass` 是数据库元数据查询,drizzle 没有对应 API。\n const probe = (await tx.execute(\n sql`SELECT to_regclass('t_schema_meta') IS NOT NULL AS exists`,\n )) as unknown as { rows: { exists: boolean }[] };\n const metaExists = probe.rows[0]?.exists === true;\n // DDL 由实体描述生成(一次一条:PG 的 extended query protocol 拒绝\n // 多语句字符串,且 DDL 事务性,逐条执行保持初始化原子)。\n for (const statement of createTablesSql(\"postgres\", postgresTableDefs)) {\n await tx.execute(sql.raw(statement));\n }\n if (!metaExists) {\n await tx\n .insert(pgSchemaMeta)\n .values([\n { fKey: \"schema_version\", fValue: String(SCHEMA_VERSION) },\n { fKey: \"application_id\", fValue: String(SESSION_PERSISTENCE_SQLITE_APPLICATION_ID) },\n ])\n .execute();\n }\n // 校验(缺版本行 = 有对象但未版本化 → 拒绝,不迁移)。\n const version = await this.readMeta(tx, \"schema_version\");\n const applicationId = await this.readMeta(tx, \"application_id\");\n if (version === undefined || applicationId === undefined) {\n throw new Error(\"session database has an unversioned schema or application identity\");\n }\n if (Number(version) !== SCHEMA_VERSION) {\n throw new Error(\n `session database has schema version ${version}, incompatible with this build (${SCHEMA_VERSION})`,\n );\n }\n if (Number(applicationId) !== SESSION_PERSISTENCE_SQLITE_APPLICATION_ID) {\n throw new Error(\n `session database has application id ${applicationId}, expected ${SESSION_PERSISTENCE_SQLITE_APPLICATION_ID}`,\n );\n }\n await tx\n .insert(pgPersistenceState)\n .values({ fSingleton: 1, fStoreId: randomUUID() })\n .onConflictDoNothing()\n .execute();\n const store = await tx\n .select({ fStoreId: pgPersistenceState.fStoreId })\n .from(pgPersistenceState)\n .where(eq(pgPersistenceState.fSingleton, 1))\n .execute();\n const storeId = store[0]?.fStoreId;\n if (storeId === undefined || storeId.length === 0) {\n throw new Error(\"session database has no valid store identity\");\n }\n return storeId;\n });\n this.storeIdentity = `${this.options.identityBase}:store:${storeId}`;\n }\n\n async close(): Promise<void> {\n await this.options.close();\n }\n\n async getSession(id: SessionId): Promise<SessionRow | undefined> {\n return (\n await this.db.select().from(pgSessions).where(eq(pgSessions.fSessionId, id)).execute()\n )[0] as SessionRow | undefined;\n }\n\n async getSeqMapRows(\n id: SessionId,\n ): Promise<Array<{ fSequence: number; fOriginalSeq: number; fKind: string }>> {\n return this.eventRows(this.db).where(eq(pgSessionEvents.fSessionId, id)).execute();\n }\n\n async getEventRows(id: SessionId, fromSequence?: number): Promise<EventRow[]> {\n const scoped =\n fromSequence === undefined\n ? this.eventRows(this.db).where(eq(pgSessionEvents.fSessionId, id))\n : this.eventRows(this.db).where(\n and(eq(pgSessionEvents.fSessionId, id), gte(pgSessionEvents.fSequence, fromSequence)),\n );\n return scoped.orderBy(pgSessionEvents.fSequence).execute() as unknown as EventRow[];\n }\n\n async listSessions(): Promise<SessionRow[]> {\n return this.db.select().from(pgSessions).execute() as unknown as Promise<SessionRow[]>;\n }\n\n async transaction<T>(fn: (tx: BackendTx) => Promise<T>): Promise<T> {\n return this.db.transaction(async (tx) => fn(this.txFor(tx)));\n }\n\n /** Bind the {@link BackendTx} primitives to one drizzle PG transaction handle. */\n private txFor(tx: PgAsyncTransaction<THKT>): BackendTx {\n return {\n upsertSession: (meta, incarnation) => this.upsertSession(tx, meta, incarnation),\n getHead: (id) => this.getHead(tx, id),\n insertEvents: (events) => this.insertEvents(tx, events),\n insertBridges: (rows) => this.insertBridges(tx, rows),\n updateHead: (id, headEventId, headSequence) =>\n this.updateHead(tx, id, headEventId, headSequence),\n bumpRevision: (id) => this.bumpRevision(tx, id),\n deleteBridgeTail: (id, fromSequence) => this.deleteBridgeTail(tx, id, fromSequence),\n getPrevBridge: (id, sequence) => this.getPrevBridge(tx, id, sequence),\n getLastBridge: (id) => this.getLastBridge(tx, id),\n updateEventFields: (id, sequence, fields) => this.updateEventFields(tx, id, sequence, fields),\n };\n }\n\n // --- meta helpers ---\n\n private async readMeta(exec: PgAsyncDatabase<THKT>, key: string): Promise<string | undefined> {\n const rows = await exec\n .select({ fValue: pgSchemaMeta.fValue })\n .from(pgSchemaMeta)\n .where(eq(pgSchemaMeta.fKey, key))\n .execute();\n return rows[0]?.fValue;\n }\n\n // --- row primitives (transaction-internal) ---\n\n private async upsertSession(\n exec: PgAsyncDatabase<THKT>,\n meta: SessionHeader,\n incarnation: string,\n ): Promise<void> {\n await exec\n .insert(pgSessions)\n .values(sessionInsertRow(meta, incarnation))\n .onConflictDoUpdate({\n target: pgSessions.fSessionId,\n set: sessionConflictRow(meta),\n })\n .execute();\n }\n\n private async getHead(\n exec: PgAsyncDatabase<THKT>,\n id: SessionId,\n ): Promise<Pick<SessionRow, \"fHeadEventId\" | \"fHeadSequence\">> {\n const head = (\n await exec\n .select({ fHeadEventId: pgSessions.fHeadEventId, fHeadSequence: pgSessions.fHeadSequence })\n .from(pgSessions)\n .where(eq(pgSessions.fSessionId, id))\n .execute()\n )[0] as Pick<SessionRow, \"fHeadEventId\" | \"fHeadSequence\"> | undefined;\n /* v8 ignore next -- appendBatch/commitRepair always materialize the row before reading the head */\n if (head === undefined) throw new Error(`session \"${id}\" has no materialized row`);\n return head;\n }\n\n private async insertEvents(exec: PgAsyncDatabase<THKT>, events: EventInsert[]): Promise<void> {\n if (events.length === 0) return;\n await exec\n .insert(pgEvents)\n .values(events.map((event) => ({ ...event })))\n .execute();\n }\n\n private async insertBridges(\n exec: PgAsyncDatabase<THKT>,\n rows: Array<{ fSessionId: SessionId; fEventId: string; fSequence: number }>,\n ): Promise<void> {\n if (rows.length === 0) return;\n await exec\n .insert(pgSessionEvents)\n .values(rows.map((row) => ({ ...row })))\n .execute();\n }\n\n private async updateHead(\n exec: PgAsyncDatabase<THKT>,\n id: SessionId,\n headEventId: string,\n headSequence: number,\n ): Promise<void> {\n await exec\n .update(pgSessions)\n .set({ fHeadEventId: headEventId, fHeadSequence: headSequence })\n .where(eq(pgSessions.fSessionId, id))\n .execute();\n }\n\n private async bumpRevision(exec: PgAsyncDatabase<THKT>, id: SessionId): Promise<void> {\n await exec\n .update(pgSessions)\n .set({ fRevision: sql`${pgSessions.fRevision} + 1` })\n .where(eq(pgSessions.fSessionId, id))\n .execute();\n }\n\n private async deleteBridgeTail(\n exec: PgAsyncDatabase<THKT>,\n id: SessionId,\n fromSequence: number,\n ): Promise<void> {\n await exec\n .delete(pgSessionEvents)\n .where(and(eq(pgSessionEvents.fSessionId, id), gte(pgSessionEvents.fSequence, fromSequence)))\n .execute();\n }\n\n private async getPrevBridge(\n exec: PgAsyncDatabase<THKT>,\n id: SessionId,\n sequence: number,\n ): Promise<{ fEventId: string; fSequence: number } | undefined> {\n return (\n await exec\n .select({ fEventId: pgSessionEvents.fEventId, fSequence: pgSessionEvents.fSequence })\n .from(pgSessionEvents)\n .where(and(eq(pgSessionEvents.fSessionId, id), eq(pgSessionEvents.fSequence, sequence)))\n .execute()\n )[0] as { fEventId: string; fSequence: number } | undefined;\n }\n\n private async getLastBridge(\n exec: PgAsyncDatabase<THKT>,\n id: SessionId,\n ): Promise<{ fEventId: string; fSequence: number } | undefined> {\n return (\n await exec\n .select({ fEventId: pgSessionEvents.fEventId, fSequence: pgSessionEvents.fSequence })\n .from(pgSessionEvents)\n .where(eq(pgSessionEvents.fSessionId, id))\n .orderBy(desc(pgSessionEvents.fSequence))\n .limit(1)\n .execute()\n )[0] as { fEventId: string; fSequence: number } | undefined;\n }\n\n /** The joined event-row projection shared by whole-log and suffix reads. */\n private eventRows(exec: PgAsyncDatabase<THKT>) {\n return exec\n .select({\n fSequence: pgSessionEvents.fSequence,\n fOriginalSeq: pgEvents.fOriginalSeq,\n fKind: pgEvents.fKind,\n fCreatedAt: pgEvents.fCreatedAt,\n fData: pgEvents.fData,\n fSourceEventSeqs: pgEvents.fSourceEventSeqs,\n fSurfaceOp: pgEvents.fSurfaceOp,\n })\n .from(pgSessionEvents)\n .innerJoin(pgEvents, eq(pgSessionEvents.fEventId, pgEvents.fEventId));\n }\n\n private async updateEventFields(\n exec: PgAsyncDatabase<THKT>,\n id: SessionId,\n sequence: number,\n fields: {\n fSourceEventSeqs?: string | null;\n fSurfaceOp?: string | null;\n fData?: string;\n },\n ): Promise<void> {\n const eventIds = exec\n .select({ fEventId: pgSessionEvents.fEventId })\n .from(pgSessionEvents)\n .where(and(eq(pgSessionEvents.fSessionId, id), eq(pgSessionEvents.fSequence, sequence)));\n await exec\n .update(pgEvents)\n .set({\n ...(fields.fSourceEventSeqs === undefined\n ? {}\n : { fSourceEventSeqs: fields.fSourceEventSeqs }),\n ...(fields.fSurfaceOp === undefined ? {} : { fSurfaceOp: fields.fSurfaceOp }),\n ...(fields.fData === undefined ? {} : { fData: fields.fData }),\n })\n .where(inArray(pgEvents.fEventId, eventIds))\n .execute();\n }\n}\n","/**\n * RDB 的「额外 provider 抽象」实现:在 `@morlay/session-rdb`\n * 既有 `PersistenceBackend`(append-only 面)之上,实现\n * `@morlay/session-branch` 的 `SessionBranchProvider`(分支面:rewind /\n * forkFrom / readBranchPrefix),并发布 `ctx.sessionBranch` 服务\n * (`SessionBranchRdb extends SessionBranch`)——使本包成为 rewind /\n * retry / fork 的持久化闭环。\n *\n * 关键设计(不改上游代码):\n *\n * - `forkFrom` 走**标准 coordinator 路径**(`create` + `append`):派生是纯\n * append(新 id / parentSession / seedLength),上游 `PersistenceCoordinator`\n * 天然支持;seed 前缀按新会话 log 重新编号(保序重编号,`sourceEventSeqs`\n * 的相对序不变,`source < seq` 校验成立)。\n * - `rewind` 是上游没有的原语(`PersistenceCoordinator` 只有 append-only +\n * torn-tail 修复),因此直接操作本后端的 `Backend` 事务(DELETE 尾部 +\n * head 游标回退 + revision bump),随后**重新 load 同步 coordinator 状态**\n * (revision 变化使 `isPreparedSourceCurrent` 失效 → 重新 adopt →\n * `state.cursor` 与新尾部一致)并更新 `WriteGuard` 确认 head。\n * - 独占条件:无 live owner(`ctx.sessions.get(id) === undefined`)。prepared\n * reservation 无法从公开 API 查询,rewind 通过 revision 变化使其自然失效。\n *\n * @module @morlay/session-rdb/branch\n */\n\nimport {\n SESSION_FORMAT_VERSION,\n type Session,\n type SessionEvent,\n type SessionHeader,\n type SessionId,\n} from \"@deepseek-ai/dsh-session\";\nimport type { SessionPersistenceSnapshot } from \"@deepseek-ai/dsh-session-persistence\";\nimport {\n SessionBranch,\n SessionBranchError,\n balanceRewindPrefix,\n buildTimeline,\n type BranchAnchorMode,\n type BranchBoundary,\n type ForkFromOptions,\n type SessionBranchProvider,\n} from \"@morlay/session-branch\";\nimport { randomUUID } from \"node:crypto\";\nimport type { SessionPersistenceRdb } from \"./index.ts\";\nimport { rowToMeta } from \"./log.ts\";\nimport { isPersistedEvent } from \"./schema.ts\";\n\n/**\n * 定位 `atSeq` 锚定的闭合 `turn/end` 边界 seq。见\n * {@link SessionBranchProvider.readBranchPrefix} 的锚定语义说明。\n * @param events - 完整(或前缀)事件列表,seq 连续。\n * @param atSeq - 锚定 seq(inclusive);省略取最后闭合轮次。\n * @param mode - `\"after\"`(默认)或 `\"before\"`。\n * @returns 边界事件 seq;`\"before\"` 模式下 atSeq 之前无闭合轮次时返回 -1\n * (空前缀)。\n */\nexport function locateTurnEnd(\n events: readonly SessionEvent[],\n atSeq?: number,\n mode: BranchAnchorMode = \"after\",\n): number {\n const ends = events.filter((event) => event.type === \"turn/end\").map((event) => event.seq);\n if (atSeq === undefined) {\n const last = ends.at(-1);\n if (last === undefined) throw new SessionBranchError(\"session has no closed turn\", \"OPEN_TURN\");\n return last;\n }\n if (mode === \"before\") {\n let boundary = -1;\n for (const seq of ends) {\n if (seq < atSeq) boundary = seq;\n else break;\n }\n return boundary;\n }\n const firstAfter = ends.find((seq) => seq >= atSeq);\n if (firstAfter !== undefined) return firstAfter;\n // atSeq 越过末尾:若其落在未闭合轮内则拒绝,否则回退最后一个闭合轮。\n const lastStart = [...events].reverse().find((event) => event.type === \"turn/start\");\n if (lastStart !== undefined && lastStart.seq <= atSeq) {\n throw new SessionBranchError(`anchor ${atSeq} lies inside an open turn`, \"OPEN_TURN\");\n }\n const last = ends.at(-1);\n if (last === undefined) throw new SessionBranchError(\"session has no closed turn\", \"OPEN_TURN\");\n return last;\n}\n\n/** 按新会话 log 重新编号:保序重编号,`sourceEventSeqs` 相对序不变。 */\nfunction renumber(events: readonly SessionEvent[], offset: number): SessionEvent[] {\n return events.map((event, index) => ({ ...event, seq: offset + index }) as SessionEvent);\n}\n\n/** 派生会话 id(后端 mint 策略,与 apiproxy `session-<uuid>` 一致)。 */\nfunction mintSessionId(): SessionId {\n return `session-${randomUUID()}` as SessionId;\n}\n\n/**\n * live rewind 所需的「live 会话/agent」最小访问面。上游 `Session` 是\n * append-only、缓存增量式对象,无公开截断方法;`PersistenceCoordinator`\n * 的 `states` 是私有 Map。这些是**运行时最小侵入**(不改上游源码,仅在\n * 编排层操作编译后对象字段),由 `SessionBranchRdb` 注入。\n */\nexport interface LiveSessionHooks {\n /** 查当前 live 会话(`ctx.sessions.get`)。 */\n getSession(id: SessionId): Session | undefined;\n /** 查当前 live agent(`ctx.agents?.get`,agents 服务可选)。 */\n getAgent(id: SessionId): LiveAgentLike | undefined;\n /** 立即落盘 live 会话的 write-behind 缓冲(`ctx.sessions.flush`)。 */\n flush(session: Session): Promise<boolean>;\n /**\n * 截断后同步 coordinator 的会话内存状态:把 `states` 条目的 `cursor`\n * 对齐到新的尾部(= boundary + 1),使下一次 append 的 seq 连续性校验\n * 通过。**不能**删除 `states` 条目——live 会话的下一次 append 若走\n * `adopt` 会经 `SessionStore.prepare` 构造 detached session,与 store 中\n * 的 live entry 冲突。\n */\n setCoordinatorCursor(id: SessionId, cursor: number): void;\n /**\n * 截断后同步 coordinator 的 ownerless 状态(cold 分支):把 `states` 条目\n * 的 `cursor` 对齐到新尾部。与 {@link setCoordinatorCursor} 的区别是\n * **不要求条目已存在**——cold 会话可能从未被 adopt(无 states 条目),\n * 此时创建 ownerless 条目(meta 来自截断后快照),使下一次 append 走\n * 标准路径而非 `adopt`(adopt 会经 `prepareCore` 补 closers 撤销截断)。\n */\n setCoordinatorState(id: SessionId, cursor: number, meta: SessionHeader): void;\n}\n\n/** agent 的最小 live 形态:持有同一会话 + 可重置请求头日志标记。 */\nexport interface LiveAgentLike {\n session: Session;\n /** 编译后字段:是否已 append 过首个 `request/header`(截断后需重置)。 */\n requestHeaderLogged?: boolean;\n}\n\n/**\n * 截断 live 会话的内存 log 并重置全部派生缓存。上游 `Session` 的\n * `log`/`surfaceManager`/`headerFold`/`contextFold`/`derived` 都是增量缓存:\n * 截断 `log` 后必须同步重置,否则下一次 append 会基于陈旧状态校验/投影。\n * 字段名取编译后产物(`@deepseek-ai/dsh-session` lib),并对\n * `SurfaceManager`(持有同一 `log` 数组引用)做全量重折叠复位——\n * `_lastProcessedSeq = baseSeq - 1` 使下次访问重新 fold 整个截断后 log。\n */\nexport function truncateLiveSession(session: Session, newLength: number): void {\n const s = session as unknown as {\n log: SessionEvent[];\n eventsSnapshot?: unknown;\n headerFold?: unknown;\n headerFoldSeq: number;\n contextFold?: unknown;\n contextFoldSeq: number;\n derived: unknown[];\n derivedNodes: number;\n derivedGeneration: number;\n surfaceManager: {\n _state: { nodes: number[]; replaceGeneration: number };\n _lastProcessedSeq: number;\n _pendingPlan?: unknown;\n baseSeq: number;\n };\n };\n s.log.length = newLength;\n s.eventsSnapshot = undefined;\n s.headerFold = undefined;\n s.headerFoldSeq = 0;\n s.contextFold = undefined;\n s.contextFoldSeq = 0;\n s.derived = [];\n s.derivedNodes = 0;\n s.derivedGeneration = 0;\n s.surfaceManager._state = { nodes: [], replaceGeneration: 0 };\n s.surfaceManager._lastProcessedSeq = s.surfaceManager.baseSeq - 1;\n s.surfaceManager._pendingPlan = undefined;\n}\n\n/**\n * RDB 分支数据层实现(`SessionBranchProvider`)。与 `SessionPersistenceRdb`\n * 共享同一数据库连接(`Backend`)与 coordinator 写路径。\n */\nexport class SessionBranchRdbProvider implements SessionBranchProvider {\n readonly name = \"session-rdb\";\n\n constructor(\n private readonly persistence: SessionPersistenceRdb,\n /** live 会话/agent 访问面(rewind 支持 live session 时使用)。 */\n private readonly live: LiveSessionHooks = {\n getSession: () => undefined,\n getAgent: () => undefined,\n flush: async () => true,\n setCoordinatorCursor: () => {},\n setCoordinatorState: () => {},\n },\n ) {}\n\n async readBranchPrefix(\n id: SessionId,\n atSeq?: number,\n mode: BranchAnchorMode = \"after\",\n signal?: AbortSignal,\n ): Promise<BranchBoundary> {\n const { events } = await this.readRawEvents(id, signal);\n const boundary = locateTurnEnd(events, atSeq, mode);\n return { seq: boundary, events: events.slice(0, boundary + 1) };\n }\n\n /**\n * 读取会话的**原始**事件(不含 coordinator 补记的合成 closers)。\n *\n * `persistence.inspect` 走 coordinator 的 `prepareCore`,会给未闭合 log\n * 补 `interruptedTurnClosers`(合成 step/end + turn/end)——对普通读取\n * (客户端历史)这是正确的逻辑视图,但对分支编排是误导:未闭合轮次被\n * closers 掩盖成闭合,编辑未闭合轮次的 user 消息会走错边界。这里用\n * `loadStored`(backend hook,scanRows 只做 torn-tail 切割、不补 closers)\n * 读原始事件,使未闭合状态真实可见。\n */\n async readRawEvents(\n id: SessionId,\n signal?: AbortSignal,\n ): Promise<{ meta: SessionHeader; events: readonly SessionEvent[] }> {\n const stored = await this.persistence.loadStored(id, signal);\n if (stored === undefined)\n throw new SessionBranchError(`session \"${id}\" not found`, \"SESSION_NOT_FOUND\");\n return { meta: stored.meta, events: stored.events };\n }\n\n async forkFrom(\n sourceId: SessionId,\n options: ForkFromOptions = {},\n signal?: AbortSignal,\n ): Promise<SessionId> {\n signal?.throwIfAborted();\n const { atSeq, anchorMode = \"after\", seedSuffix = [], childSessionId, meta = {} } = options;\n const source = await this.persistence.inspect(sourceId, signal);\n const boundary = locateTurnEnd(source.events, atSeq, anchorMode);\n const prefix = source.events.slice(0, boundary + 1);\n const childId = childSessionId ?? mintSessionId();\n const childMeta: SessionHeader = {\n version: SESSION_FORMAT_VERSION,\n id: childId,\n createdAt: meta.createdAt ?? Date.now(),\n ...(meta.cwd !== undefined\n ? { cwd: meta.cwd }\n : source.meta.cwd !== undefined\n ? { cwd: source.meta.cwd }\n : {}),\n parentSession: sourceId,\n seedLength: prefix.length,\n ...(meta.agentPreset !== undefined\n ? { agentPreset: meta.agentPreset }\n : source.meta.agentPreset !== undefined\n ? { agentPreset: source.meta.agentPreset }\n : {}),\n ...(meta.origin !== undefined ? { origin: meta.origin } : {}),\n ...(meta.delegationDepth !== undefined ? { delegationDepth: meta.delegationDepth } : {}),\n };\n const seed = [...renumber(prefix, 0), ...renumber(seedSuffix, prefix.length)];\n await this.persistence.create(childMeta);\n if (seed.length > 0) await this.persistence.append(childId, seed);\n return childId;\n }\n\n async rewind(\n id: SessionId,\n toBoundary: number,\n signal?: AbortSignal,\n ): Promise<SessionPersistenceSnapshot> {\n signal?.throwIfAborted();\n if (!Number.isSafeInteger(toBoundary) || toBoundary < -1) {\n throw new SessionBranchError(\n `rewind boundary must be a non-negative safe integer, got ${toBoundary}`,\n \"INVALID_BOUNDARY\",\n );\n }\n const live = this.live.getSession(id);\n // live 会话先把 write-behind 缓冲全部落盘(RDB = live 内存全量),保证\n // 后续读取与后端事务(读 RDB head)基于同一视图。\n if (live !== undefined) await this.live.flush(live);\n // 边界校验:toBoundary 是已存在事件且为 turn/end 或 user/message。\n // 用**原始**事件(loadStored,不补 closers)——`inspect` 会经 coordinator\n // 的 `prepareCore` 给未闭合 log 补合成 step/end + turn/end,把 user/message\n // 边界掩盖成 turn/end,导致 exclusive 语义丢失。\n const raw = live === undefined ? await this.readRawEvents(id, signal) : undefined;\n const inspection = live === undefined ? undefined : await this.persistence.inspect(id, signal);\n const events = live === undefined ? raw!.events : inspection!.events;\n const meta = live === undefined ? raw!.meta : inspection!.meta;\n const boundaryEvent = events[toBoundary];\n if (toBoundary === -1) {\n // 空前缀:允许回退到「任何轮次之前」= 清空整个 log 的前置(head 归 -1)。\n // 与 commitRepair 的「head 回退到初始状态」语义一致。\n } else if (boundaryEvent === undefined) {\n throw new SessionBranchError(\n `rewind boundary ${toBoundary} does not exist in session \"${id}\"`,\n \"INVALID_BOUNDARY\",\n );\n } else if (boundaryEvent.type !== \"turn/end\" && boundaryEvent.type !== \"user/message\") {\n throw new SessionBranchError(\n `rewind boundary ${toBoundary} is not a turn/end or user/message (${boundaryEvent.type})`,\n \"INVALID_BOUNDARY\",\n );\n }\n // 保留前缀长度(上游坐标):turn/end 保留到该事件(inclusive);user/message\n // drop 该消息及其后(exclusive)——编辑重放语义,边界消息由编辑版替换。\n // 随后平衡化:真实 agent-loop 的 step/start 在 user/message 之前,exclusive\n // 截断会残留未配对的 step/start(其 step/end 在 drop 区)——token meter\n // 重放要求 step/start 配对,因此从尾部剔除孤儿 step/start(见\n // balanceRewindPrefix)。\n const rawKeepLength =\n toBoundary === -1\n ? 0\n : boundaryEvent!.type === \"turn/end\"\n ? toBoundary + 1\n : toBoundary;\n const keepLength = balanceRewindPrefix(events.slice(0, rawKeepLength)).length;\n\n const internals = this.persistence.internals();\n // —— 坐标换算:上游 seq(live log / 编排层 boundary)→ RDB 稠密 seq ——\n // live 会话的 `inspection.events` 是内存 log 的上游 seq:delta 事件\n // (assistant/chunk)与 ignorable 事件(版本效果)也占用连续 seq,而\n // RDB 只在写路径持久化 survived 事件并稠密重编号(f_sequence),\n // `head.fHeadSequence` 是稠密坐标。rewind 的 boundary 来自编排层基于\n // live log 计算的上游 seq,必须映射为 RDB 的删除目标 = 前缀中\n // persisted 事件数 - 1;否则「boundary > stored head」恒误报(上游\n // head 永远大于稠密 head)。cold 视图本身已是稠密 seq,直接使用。\n const denseBoundary =\n live === undefined\n ? keepLength - 1\n : events.slice(0, keepLength).filter(isPersistedEvent).length - 1;\n await internals.backend.transaction(async (tx) => {\n signal?.throwIfAborted();\n const head = await tx.getHead(id);\n if (denseBoundary > head.fHeadSequence) {\n throw new SessionBranchError(\n `rewind boundary ${toBoundary} is beyond the stored head ${head.fHeadSequence}`,\n \"INVALID_BOUNDARY\",\n );\n }\n if (denseBoundary < head.fHeadSequence) {\n await tx.deleteBridgeTail(id, denseBoundary + 1);\n const prev = denseBoundary === -1 ? undefined : await tx.getPrevBridge(id, denseBoundary);\n if (prev === undefined) {\n await tx.updateHead(id, \"\", -1);\n } else {\n await tx.updateHead(id, prev.fEventId, prev.fSequence);\n }\n }\n await tx.bumpRevision(id);\n });\n\n // 更新本实例的确认 head(下一次 append 的并发校验基准)——稠密坐标,\n // 与 `appendBatch` 内 `confirmHead` 的稠密语义一致。\n internals.writeGuard.confirmHead(id, denseBoundary);\n\n if (live !== undefined) {\n // live 分支:截断内存 log 并重置派生缓存;同步 coordinator 的\n // state.cursor(保留 owner,下一次 append 的 seq 校验续接截断后\n // 尾部);重置 agent 的请求头标记(截断可能删掉最后一条\n // request/header,让 agent 下次补记)。不调用 `load`——live 时 load\n // 会先 flush 把旧内存写回,撤销本次截断。\n truncateLiveSession(live, keepLength);\n const agent = this.live.getAgent(id);\n if (agent !== undefined) {\n agent.requestHeaderLogged = false;\n // 同步 agent 的轮次游标:截断后 session 的最后一个 turn 号。live\n // agent 驻留不重建,其 `phase.lastTurn` 仍是编辑前的值——不重置的话\n // 重放(followup)会继续递增产生**新**轮次号(如重试 turn 2 得到\n // turn 3),而不是复用目标轮号。\n const lastTurn = live.events.findLast((e) => e.type === \"turn/start\")?.data.turn ?? 0;\n const phase = (agent as unknown as { phase?: { lastTurn?: number } }).phase;\n if (phase !== undefined) phase.lastTurn = lastTurn;\n }\n this.live.setCoordinatorCursor(id, keepLength);\n } else {\n // cold 分支:同步 coordinator 状态。\n // - turn/end 边界:截断后 log 平衡(停在闭合轮次末尾),load 的\n // repair 为 no-op,经 revision 变化重新 adopt 使 state.cursor 与\n // 新尾部一致;\n // - user/message 边界:截断后 log 停在 turn/start(不平衡),load 的\n // `prepareCore` 会用 `interruptedTurnClosers` 补 closers 并 commitRepair\n // 落盘——把被 drop 的 user/message 的 seq 位置占掉,撤销截断语义。\n // 因此直接同步 ownerless states 条目(cursor = keepLength),下一次\n // append 走标准路径而非 adopt。\n if (boundaryEvent?.type === \"user/message\") {\n this.live.setCoordinatorState(id, keepLength, meta);\n } else {\n await this.persistence.load(id);\n }\n }\n\n const row = await internals.backend.getSession(id);\n if (row === undefined) {\n // 空会话(toBoundary = -1 且从未有行):返回「已确认缺席」快照。\n return {\n header: {\n version: SESSION_FORMAT_VERSION,\n id,\n createdAt: meta.createdAt,\n ...(meta.cwd !== undefined ? { cwd: meta.cwd } : {}),\n ...(meta.parentSession !== undefined ? { parentSession: meta.parentSession } : {}),\n ...(meta.seedLength !== undefined ? { seedLength: meta.seedLength } : {}),\n ...(meta.origin !== undefined ? { origin: meta.origin } : {}),\n ...(meta.delegationDepth !== undefined\n ? { delegationDepth: meta.delegationDepth }\n : {}),\n ...(meta.agentPreset !== undefined ? { agentPreset: meta.agentPreset } : {}),\n },\n revision:\n (await internals.readStoredRevision(id)) ??\n (await this.persistence.readStoredRevision(id))!,\n };\n }\n return { header: rowToMeta(row), revision: (await internals.readStoredRevision(id))! };\n }\n}\n\n/**\n * RDB 的 `ctx.sessionBranch` 服务:组合 {@link SessionBranchRdbProvider} 的\n * 数据层原语与共享版本树投影。插件把本类注册为 `sessionBranch` 服务后,\n * 编排层即可通过统一服务面完成 rewind / retry / fork。\n */\nexport class SessionBranchRdb extends SessionBranch {\n static inject = [\"sessionPersistence\", \"sessions\"];\n\n constructor(ctx: import(\"@deepseek-ai/cordis\").Context) {\n super(ctx);\n }\n\n private readonly provider = new SessionBranchRdbProvider(\n this.ctx.sessionPersistence as SessionPersistenceRdb,\n {\n getSession: (id) => this.ctx.sessions.get(id),\n getAgent: (id) => {\n // agents 服务可选(纯持久化环境无 agent-loop):经 ctx.get 动态访问。\n const agents = this.ctx.get(\"agents\") as\n | { get(id: SessionId): LiveAgentLike | undefined }\n | undefined;\n return agents?.get(id);\n },\n flush: (session) => this.ctx.sessions.flush(session),\n setCoordinatorCursor: (id, cursor) => {\n // coordinator 的 states 是私有 Map;保留条目(owner 不能丢),仅把\n // cursor 对齐到截断后的新尾部,使下一次 append 的 seq 校验续接。\n const persistence = this.ctx.sessionPersistence as unknown as {\n coordinator?: {\n states?: Map<SessionId, { cursor: number } | undefined>;\n };\n };\n const state = persistence.coordinator?.states?.get(id);\n if (state !== undefined) state.cursor = cursor;\n },\n setCoordinatorState: (id, cursor, meta) => {\n // cold 分支(user/message 边界):states 条目可能不存在(会话从未\n // 被 adopt)。存在则仅对齐 cursor;不存在则创建 ownerless 条目\n // (meta 来自截断后快照),使下一次 append 走标准路径而非 adopt\n // (adopt 会经 prepareCore 补 closers 撤销截断)。\n const persistence = this.ctx.sessionPersistence as unknown as {\n coordinator?: {\n states?: Map<SessionId, { cursor: number; meta: SessionHeader; materialized: boolean } | undefined>;\n };\n };\n const states = persistence.coordinator?.states;\n if (states === undefined) return;\n const state = states.get(id);\n if (state !== undefined) {\n state.cursor = cursor;\n } else {\n states.set(id, { meta, cursor, materialized: true });\n }\n },\n },\n );\n\n readBranchPrefix(\n id: SessionId,\n atSeq?: number,\n mode?: BranchAnchorMode,\n signal?: AbortSignal,\n ): Promise<BranchBoundary> {\n return this.provider.readBranchPrefix(id, atSeq, mode, signal);\n }\n\n /**\n * 读取会话的**原始**事件(不含 coordinator 补记的合成 closers)。\n * 编排层用它识别未闭合轮次(inspect 会把未闭合 log 补成闭合)。\n */\n readRawEvents(\n id: SessionId,\n signal?: AbortSignal,\n ): Promise<{ meta: SessionHeader; events: readonly SessionEvent[] }> {\n return this.provider.readRawEvents(id, signal);\n }\n\n forkFrom(\n sourceId: SessionId,\n options?: ForkFromOptions,\n signal?: AbortSignal,\n ): Promise<SessionId> {\n return this.provider.forkFrom(sourceId, options, signal);\n }\n\n rewind(\n id: SessionId,\n toBoundary: number,\n signal?: AbortSignal,\n ): Promise<SessionPersistenceSnapshot> {\n return this.provider.rewind(id, toBoundary, signal);\n }\n\n /**\n * 清洗一个 session 的 surface/provenance 坐标到稠密空间(持久化层透传)。\n * 旧数据(rewind 前写入)的 sourceEventSeqs / surfaceOp / shadowedRange\n * 是上游坐标,读取时每次都要对齐;清洗一次性写回稠密坐标,之后读取无需\n * 对齐。返回实际变更的事件数。\n */\n cleanseSession(sessionId: SessionId, signal?: AbortSignal): Promise<{ changed: number }> {\n return (this.ctx.sessionPersistence as SessionPersistenceRdb).cleanseSession(sessionId, signal);\n }\n\n /**\n * 同步 live 会话的 coordinator 内存 cursor,跳过 ignorable 占位事件。\n * 编排层在 rewind 后把 ignorable 版本效果 push 进 live log(不发布、不\n * 进缓冲)——它占用一个上游 seq,但 coordinator 的 cursor(rewind 设到\n * 截断后长度)看不见它;不跳过的话,flush 的 `appendLiveBatch` 会以\n * `e.seq >= cursor` 过滤掉 cursor 之前的事件(manualTurn 永远不落盘),\n * 或 `appendCore` 的 seq 连续性校验错位。这里把 cursor 从当前值起跳过\n * 连续的 ignorable 事件,对齐到下一个待持久化事件的 seq。\n */\n syncLiveCursor(sessionId: SessionId): void {\n const live = this.ctx.sessions.get(sessionId);\n if (live === undefined) return;\n const persistence = this.ctx.sessionPersistence as unknown as {\n coordinator?: {\n states?: Map<SessionId, { cursor: number } | undefined>;\n };\n };\n const state = persistence.coordinator?.states?.get(sessionId);\n if (state === undefined) return;\n let cursor = state.cursor;\n while (live.events[cursor]?.ignorable === true) cursor += 1;\n state.cursor = cursor;\n }\n\n async timeline(sessionId: SessionId, signal?: AbortSignal) {\n const persistence = this.ctx.sessionPersistence as SessionPersistenceRdb;\n const snapshots = await persistence.listSnapshots(signal);\n // live 会话从内存 log 读自有后缀(含 ignorable 版本效果事件);cold 会话\n // 走持久化 readFrom——版本效果不落 canonical log(ignorable 语义),\n // cold timeline 退化为 lineage 骨架(无效果详情)。\n const readOwnEvents = async (id: SessionId, fromSeq: number, s?: AbortSignal) => {\n const live = this.ctx.sessions.get(id);\n if (live !== undefined) return live.events.slice(fromSeq);\n return (await persistence.readFrom(id, fromSeq, s)).events;\n };\n return buildTimeline(snapshots, readOwnEvents, sessionId, signal);\n }\n}\n\nexport default SessionBranchRdb;\n","/**\n * SQLite / PostgreSQL durable session-persistence backend.\n *\n * Storage follows the playpen-session store: `t_sessions` carries the header\n * and a head cursor, `t_events` stores each event as a globally addressable\n * entity (event id + parent chain + kind/role/name/action-id dimensions), and\n * `t_session_events` bridges sessions to events in per-session seq order.\n * Delta content (`assistant/chunk`) is never persisted: those events are\n * dropped and the surviving events are re-numbered to a dense persisted seq\n * (`f_original_seq` keeps the upstream seq for provenance remapping).\n *\n * The database is chosen by configuration (discriminated union on `type`):\n * `{ type: \"sqlite\", path }` or `{ type: \"postgres\", connectionString }`.\n * All access goes through drizzle; the schema is declared once per dialect\n * (`schema.ts` / `postgres.ts`) and the hand-written DDL there is the only\n * migration story (no migration toolchain — incompatible stores are rejected,\n * never migrated).\n *\n * It delegates write-path orchestration to {@link PersistenceCoordinator} and\n * has no independent per-session artifact, so its locator returns `undefined`.\n * @module @morlay/session-rdb\n */\n\nimport { Context } from \"@deepseek-ai/cordis\";\nimport z from \"@deepseek-ai/schemastery\";\nimport { settingsNamespace, type SettingsProvider } from \"@deepseek-ai/dsh-settings\";\nimport { randomUUID } from \"node:crypto\";\nimport { Pool } from \"pg\";\nimport { drizzle as drizzlePg } from \"drizzle-orm/node-postgres\";\nimport {\n SessionPersistence,\n SessionPersistenceRevision,\n PersistenceCoordinator,\n type PersistenceBackend,\n type SessionLocation,\n type SessionPersistenceSnapshot,\n type StoredPrefix,\n type StoredSuffix,\n} from \"@deepseek-ai/dsh-session-persistence\";\nimport type {\n SessionEvent,\n SurfaceEventType,\n SessionId,\n SessionHeader,\n} from \"@deepseek-ai/dsh-session\";\nimport { type Backend, type BackendTx, type EventInsert, type EventRow } from \"./backend.ts\";\nimport { WriteGuard } from \"./write-guard.ts\";\nimport { buildSeqMap, normalizeSurfaceReplaceProvenance, rowToMeta, scanRows } from \"./log.ts\";\nimport {\n DEFAULT_BUSY_TIMEOUT_MS,\n eventDimensions,\n EVENT_ENCODING,\n isPersistedEvent,\n type JournalMode,\n} from \"./schema.ts\";\nimport { SqliteBackend } from \"./sqlite.ts\";\nimport { PostgresBackend } from \"./postgres.ts\";\nimport { SessionBranchRdb } from \"./branch.ts\";\n\nexport { SCHEMA_VERSION, EPHEMERAL_EVENT_TYPES } from \"./schema.ts\";\nexport { SessionBranchRdb, SessionBranchRdbProvider, locateTurnEnd } from \"./branch.ts\";\n\n/**\n * 同包分支 provider(rewind / forkFrom)访问 `SessionPersistenceRdb` 内部\n * 能力的窄接口。避免把 backend / writeGuard 暴露成公开 API,同时让\n * {@link SessionBranchRdbProvider} 与持久化后端共享同一连接与写路径。\n * @internal\n */\nexport interface SessionPersistenceRdbInternals {\n readonly backend: Backend;\n readonly writeGuard: WriteGuard;\n create(meta: SessionHeader): Promise<void>;\n append(id: SessionId, events: readonly SessionEvent[]): Promise<void>;\n load(id: SessionId): Promise<import(\"@deepseek-ai/dsh-session-persistence\").SessionInspection>;\n inspect(\n id: SessionId,\n signal?: AbortSignal,\n ): Promise<import(\"@deepseek-ai/dsh-session-persistence\").SessionInspection>;\n readFrom(\n id: SessionId,\n fromSeq: number,\n signal?: AbortSignal,\n ): Promise<{ meta: SessionHeader; events: SessionEvent[] }>;\n listSnapshots(signal?: AbortSignal): Promise<SessionPersistenceSnapshot[]>;\n readStoredRevision(\n id: SessionId,\n signal?: AbortSignal,\n ): Promise<import(\"@deepseek-ai/dsh-session-persistence\").SessionPersistenceRevision | undefined>;\n}\n\n/**\n * Plugin configuration — a discriminated union on `type`. The SQLite arm keeps\n * the file path plus the SQLite-specific pragmas; the PostgreSQL arm takes a\n * `node-postgres` connection string.\n */\nexport type Config =\n | {\n type: \"sqlite\";\n /**\n * Filesystem path to the SQLite database file. The special value `:memory:`\n * opens an in-process database (tests). On filesystems with POSIX modes,\n * missing directories and databases are created owner-only; existing path\n * modes are preserved.\n */\n path: string;\n /**\n * SQLite `journal_mode` pragma. `wal` (the default) is the recorded\n * durability model; pick a rollback-journal mode (`delete`/`truncate`/\n * `persist`) on filesystems where WAL's shared-memory files do not work\n * (network mounts). See {@link JournalMode}.\n */\n journalMode?: JournalMode;\n /**\n * Milliseconds to wait for a contended write lock before failing. SQLite\n * fails immediately by default, so a second process sharing this database\n * would lose every append that meets an in-flight commit; a nonzero wait\n * turns the contention window into a queue. `0` restores fail-fast.\n */\n busyTimeout?: number;\n }\n | {\n type: \"postgres\";\n /**\n * `node-postgres` connection string (e.g.\n * `postgres://user:pass@host:5432/db`). The database must be reachable;\n * the backend creates its tables and identity on first open.\n */\n connectionString: string;\n };\n\n/**\n * The persistence backend. Load as a plugin; it registers as\n * `ctx.sessionPersistence` and (via the coordinator) installs the write-path\n * listeners. Its torn-tail marker is the persisted seq to delete from.\n *\n * Configuration resolution: `$DSH_HOME/settings.yaml` 的\n * `session-rdb` namespace(settings 服务)覆盖 cordis 层 entry\n * config,见 {@link installSettingsSection}。\n */\nexport class SessionPersistenceRdb\n extends SessionPersistence\n implements PersistenceBackend<number>\n{\n static inject = [\"sessions\", \"settings\"];\n\n static Config: z<Config> = z.union([\n z.object({\n type: z.const(\"sqlite\"),\n path: z.string().required(),\n journalMode: z.union([\"wal\", \"delete\", \"truncate\", \"persist\"] as const).default(\"wal\"),\n busyTimeout: z.number().step(1).min(0).default(DEFAULT_BUSY_TIMEOUT_MS),\n }),\n z.object({\n type: z.const(\"postgres\"),\n connectionString: z.string().required(),\n }),\n ]);\n\n /** settings namespace:`$DSH_HOME/settings.yaml` 的 `session-rdb` section。 */\n static readonly settingsNs = settingsNamespace(\"session-rdb\");\n\n /**\n * Backend label for the coordinator's dispose diagnostics. Intentionally\n * shadows cordis `Service.name` (set to `'sessionPersistence'` by the base);\n * see the JSONL backend for why this does not affect service resolution.\n */\n override readonly name = \"session-rdb\";\n\n /** One RDB database holds every session; there is no per-session raw artifact. */\n override readonly supportsRawArtifacts = false;\n\n private readonly backend: Backend;\n private storeIdentity!: string;\n private readonly ready: Promise<void>;\n private readonly coordinator: PersistenceCoordinator<number>;\n /**\n * Write-authority state: the confirmed dense head per session (concurrent-\n * writer detection) and the dropped delta seqs per session (provenance\n * pruning). See {@link WriteGuard} for the timing contract.\n */\n private readonly writeGuard = new WriteGuard();\n\n constructor(\n ctx: Context,\n public config: Config,\n /**\n * @internal Test injection: use a pre-built backend (e.g. a drizzle PG\n * instance over an in-memory pglite) instead of {@link createBackend}.\n */\n injectedBackend?: Backend,\n ) {\n // settings.yaml 的 `session-rdb` namespace 覆盖 cordis 层 entry\n // config(base)。settings 服务已注册时(dsh 环境;服务注册完成即初始\n // publish 完成,见 SettingsProvider[Service.init])同步 register 读取;settings\n // 服务缺失时(纯 cordis 装配/测试)退化为 entry config。经 ctx.reflect\n // 查询避免未 inject 的 ctx 服务访问守卫。\n let resolved: Config = config;\n const settings = ctx.reflect.get(\"settings\") as unknown as SettingsProvider | undefined;\n if (settings !== undefined) {\n const scope = settings.register(\n SessionPersistenceRdb.settingsNs,\n SessionPersistenceRdb.Config,\n { base: config },\n );\n resolved = scope.get();\n scope.watch(() => {\n // 后端在构造时建成(数据库连接 + coordinator 写路径监听),settings\n // 变更后需重启 dsh 生效;热重建会与 coordinator 的持久状态冲突。\n ctx.logger.warn(\"session-rdb: settings changed; restart to apply the new configuration\");\n });\n }\n super(ctx);\n // Open asynchronously so connection setup (file creation / DB connect +\n // schema check) does not block plugin apply; every storage hook awaits the\n // same readiness promise.\n this.config = resolved;\n this.backend = injectedBackend ?? createBackend(resolved);\n this.ready = this.init();\n this.coordinator = new PersistenceCoordinator<number>(this.ctx, this);\n // 分支 provider 服务(闭环):本包同时实现 session-persistence 与\n // session-branch(rewind / forkFrom / timeline)。Service 构造即注册\n // ctx.sessionBranch 并随 fiber 卸载自动回滚。\n new SessionBranchRdb(this.ctx);\n }\n\n private async init(): Promise<void> {\n await this.backend.open();\n this.storeIdentity = this.backend.storeIdentity;\n }\n\n // --- SessionPersistence service surface (delegated to the coordinator) ---\n\n /** The backend has one database, not an independent local artifact per session. */\n locate(_meta: SessionHeader): SessionLocation | undefined {\n return undefined;\n }\n\n create(meta: SessionHeader): Promise<void> {\n return this.coordinator.create(meta);\n }\n\n append(id: SessionId, events: readonly SessionEvent[]): Promise<void> {\n return this.coordinator.append(id, events);\n }\n\n load(id: SessionId): Promise<{ meta: SessionHeader; events: readonly SessionEvent[] }> {\n return this.coordinator.load(id);\n }\n\n inspect(\n id: SessionId,\n signal?: AbortSignal,\n ): Promise<{ meta: SessionHeader; events: readonly SessionEvent[] }> {\n return this.coordinator.inspect(id, signal);\n }\n\n readFrom(\n id: SessionId,\n fromSeq: number,\n signal?: AbortSignal,\n ): Promise<{ meta: SessionHeader; events: SessionEvent[] }> {\n return this.coordinator.readFrom(id, fromSeq, signal);\n }\n\n // One method serves both public `list` and the backend hook; delegating it to\n // the coordinator would call this hook recursively.\n\n // --- PersistenceBackend hooks (the storage primitives) ---\n\n /** Read a stored prefix by id (ids are globally unique — no scope to scan). */\n loadStored(id: SessionId, signal?: AbortSignal): Promise<StoredPrefix<number> | undefined> {\n return this.readPrefix(id, signal);\n }\n\n /**\n * Seek-capable suffix read: the backend selects `f_sequence >= fromSeq`\n * directly, so the read scales with the suffix, not the log. Provenance\n * remapping still needs every row's upstream seq, so a lightweight\n * two-column map is read alongside. Torn rows past the preserved region are\n * dropped, never repaired (non-mutating read).\n */\n async loadStoredFrom(\n id: SessionId,\n fromSeq: number,\n signal?: AbortSignal,\n ): Promise<StoredSuffix | undefined> {\n const log = await this.readLog(id, { fromSeq }, signal);\n if (log === undefined) return undefined;\n return { meta: log.meta, events: log.events };\n }\n\n /**\n * Read a session's row + ordered events into a {@link StoredPrefix}. The\n * torn-tail marker is the persisted seq from which a never-committed tail\n * must be deleted (`scanRows` already returns it as `number | undefined`).\n * Records the confirmed dense head (or confirmed absence) so a later\n * `appendBatch` can detect a second writer that advanced the log.\n */\n private async readPrefix(\n id: SessionId,\n signal?: AbortSignal,\n ): Promise<StoredPrefix<number> | undefined> {\n const log = await this.readLog(id, {}, signal);\n if (log === undefined) {\n // Confirmed absence: a fresh session this instance has read about. A\n // later append to a session that meanwhile got a row must reject.\n this.writeGuard.confirmHead(id, -1);\n return undefined;\n }\n // The confirmed head is the last PRESERVED seq (a torn tail is removed by\n // the caller's commitRepair, which re-confirms the head after repair).\n this.writeGuard.confirmHead(id, log.events.at(-1)?.seq ?? -1);\n // 全量读取时使 replace 的 provenance 覆盖其 range(rewind 复用上游 seq\n // 空间后,保留的 checkpoint replace 可能引用已删除的行 → 读取时引用被\n // 剪,shadowed 校验失败 → 会话无法加载;这里把 range 内所有事件 seq 并入\n // provenance,见 {@link normalizeSurfaceReplaceProvenance})。\n normalizeSurfaceReplaceProvenance(log.events);\n return {\n meta: log.meta,\n events: log.events,\n // The revision must identify exactly these values and match\n // readStoredRevision's representation (see listSnapshots).\n revision: SessionPersistenceRevision(\n `${this.storeIdentity}:incarnation:${log.incarnation}:revision:${log.revision}`,\n ),\n ...(log.tornFrom !== undefined ? { tornMarker: log.tornFrom } : {}),\n };\n }\n\n /**\n * Read the current source-qualified revision for one stored session without\n * loading its event log. Returns `undefined` when the identity is absent.\n * The representation matches {@link loadStored}'s `revision` and\n * {@link listSnapshots} — the coordinator compares them with `===`.\n */\n async readStoredRevision(\n id: SessionId,\n signal?: AbortSignal,\n ): Promise<SessionPersistenceRevision | undefined> {\n signal?.throwIfAborted();\n await this.ready;\n signal?.throwIfAborted();\n const row = await this.backend.getSession(id);\n if (row === undefined) return undefined;\n return SessionPersistenceRevision(\n `${this.storeIdentity}:incarnation:${row.fIncarnation}:revision:${row.fRevision}`,\n );\n }\n\n /**\n * Shared read pipeline: session row → meta, event rows → preserved prefix.\n * A whole-log read (`fromSeq` absent) builds the seq map from the same rows;\n * a suffix read keeps the backend's lightweight two-column seq-map source so\n * the query still scales with the suffix, not the log.\n */\n private async readLog(\n id: SessionId,\n options: { fromSeq?: number } = {},\n signal?: AbortSignal,\n ): Promise<\n | {\n meta: SessionHeader;\n events: SessionEvent[];\n tornFrom?: number;\n /** The session row's stable identity (see {@link listSnapshots}). */\n incarnation: string;\n /** The session row's monotonic log-change token. */\n revision: number;\n }\n | undefined\n > {\n signal?.throwIfAborted();\n await this.ready;\n signal?.throwIfAborted();\n const row = await this.backend.getSession(id);\n if (row === undefined) return undefined;\n const meta = rowToMeta(row);\n let eventRows: EventRow[];\n let seqMap: ReadonlyMap<number, number> | undefined;\n let denseTypeMap: ReadonlyMap<number, string> | undefined;\n if (options.fromSeq === undefined) {\n // Whole-log read: build the seq map from the same rows (no extra query).\n eventRows = await this.backend.getEventRows(id);\n seqMap = buildSeqMap(eventRows);\n denseTypeMap = new Map(eventRows.map((r) => [r.fSequence, r.fKind]));\n } else {\n // Suffix read: rows are only the suffix, but provenance remapping needs\n // every row's upstream seq and dense type, so a lightweight two-column\n // map is read alongside — the query still scales with the suffix.\n eventRows = await this.backend.getEventRows(id, options.fromSeq);\n const seqRows = await this.backend.getSeqMapRows(id);\n seqMap = buildSeqMap(seqRows);\n denseTypeMap = new Map(seqRows.map((r) => [r.fSequence, r.fKind]));\n }\n signal?.throwIfAborted();\n const { preserved, tornFrom } = scanRows(eventRows, options.fromSeq ?? 0, seqMap, denseTypeMap);\n return {\n meta,\n events: preserved,\n incarnation: row.fIncarnation,\n revision: row.fRevision,\n ...(tornFrom !== undefined ? { tornFrom } : {}),\n };\n }\n\n /**\n * Durably append a batch in ONE transaction: materialize the sessions row (if\n * lazy) and INSERT every persisted event (plus its bridge row), or roll back\n * entirely. Delta events and events the writer marked `ignorable` are dropped\n * and the surviving events are re-numbered densely from the session's head\n * cursor; a batch that contains only dropped events is a no-op (no row\n * materialization, no revision bump). Dropped events' upstream seqs are\n * recorded per session so a later batch's surface provenance can prune\n * references to them (see {@link surfaceBindings}).\n * The transaction is the atomicity + durability boundary, so a mid-batch\n * failure (a UNIQUE violation on a duplicated seq) leaves the stored log\n * untouched.\n *\n * SQLite acquires the write lock up front (`BEGIN IMMEDIATE`, queued behind\n * `busy_timeout`); PostgreSQL relies on the transaction's row locks and the\n * `UNIQUE (f_session_id, f_sequence)` constraint to reject a colliding batch.\n * Either way {@link assertNoConcurrentWriter} rejects a second writer before\n * re-numbering — a session has exactly one writer per log, and a second\n * writer fails loud instead of corrupting the log.\n *\n * The row upsert runs UNCONDITIONALLY, not only when `!isMaterialized`: a\n * delta-only batch leaves the coordinator's materialized flag true while no\n * row exists, so the flag cannot be trusted as the row's existence signal.\n * The upsert keeps an existing row's head cursor (only header columns are\n * refreshed on conflict), so a fresh row still starts at the initial head.\n */\n async appendBatch(\n meta: SessionHeader,\n events: readonly SessionEvent[],\n _isMaterialized: boolean,\n ): Promise<void> {\n await this.ready;\n // Record every dropped delta's UPSTREAM seq (pure-delta batches included)\n // so a later batch's assistant/message can prune sourceEventSeqs references\n // to events that never got a persisted row (see {@link surfaceBindings}).\n const droppedSeqs = new Set<number>();\n for (const event of events) {\n if (!isPersistedEvent(event)) droppedSeqs.add(event.seq);\n }\n if (droppedSeqs.size > 0) this.writeGuard.noteDropped(meta.id, droppedSeqs);\n const persisted = events.filter(isPersistedEvent);\n if (persisted.length === 0) return;\n let confirmedHead = -1;\n await this.backend.transaction(async (tx) => {\n await tx.upsertSession(meta, randomUUID());\n const head = await tx.getHead(meta.id);\n // Reject a second writer BEFORE re-numbering: each coordinator instance\n // maintains its own upstream cursor, so a second instance (or process)\n // sharing this database would append through a stale view of the log —\n // the batch's events would be silently re-numbered onto the other\n // writer's tail and corrupt the log. The on-disk head must equal the\n // last head this instance confirmed (via its own writes or loadStored).\n this.writeGuard.assertNoConcurrentWriter(meta.id, head.fHeadSequence);\n const { headEventId, headSequence } = await appendEventTail(\n tx,\n meta,\n persisted,\n { parentId: head.fHeadEventId, nextSeq: head.fHeadSequence + 1 },\n (refs) => this.writeGuard.pruneRefs(meta.id, refs),\n );\n await tx.updateHead(meta.id, headEventId, headSequence);\n await tx.bumpRevision(meta.id);\n confirmedHead = headSequence;\n });\n // Confirm the new head only after the commit: a rollback must not leave\n // a confirmed head this instance did not actually write.\n this.writeGuard.confirmHead(meta.id, confirmedHead);\n }\n\n /**\n * Make a crash repair durable in ONE transaction: DELETE the torn tail (from\n * `tornMarker`), rewind the head cursor to the last surviving event, INSERT\n * the synthetic `closers`, and bump the revision once. After COMMIT the\n * stored rows == the balanced log.\n */\n async commitRepair(\n meta: SessionHeader,\n tornMarker: number | undefined,\n closers: readonly SessionEvent[],\n ): Promise<void> {\n await this.ready;\n const persistedClosers = closers.filter(isPersistedEvent);\n if (tornMarker === undefined && persistedClosers.length === 0) return;\n await this.backend.transaction(async (tx) => {\n if (tornMarker !== undefined) {\n await tx.deleteBridgeTail(meta.id, tornMarker);\n // The head cursor rewinds to the last surviving event (or the initial\n // state when the torn tail started at seq 0).\n const prev = await tx.getPrevBridge(meta.id, tornMarker - 1);\n if (prev === undefined) {\n await tx.updateHead(meta.id, \"\", -1);\n } else {\n await tx.updateHead(meta.id, prev.fEventId, prev.fSequence);\n }\n }\n if (persistedClosers.length > 0) {\n // Anchor at the ACTUAL tail row: the head cursor can lag the rows (a\n // hand-written torn tail never updated it), so a closer must follow the\n // last physical row, not the cursor.\n const last = await tx.getLastBridge(meta.id);\n const { headEventId, headSequence } = await appendEventTail(tx, meta, persistedClosers, {\n parentId: last?.fEventId ?? \"\",\n nextSeq: (last?.fSequence ?? -1) + 1,\n });\n await tx.updateHead(meta.id, headEventId, headSequence);\n }\n await tx.bumpRevision(meta.id);\n });\n // Re-confirm the head AFTER repair: truncation can rewind it and the\n // closers advance it, and the next append must not be rejected (or worse,\n // silently re-numbered) against a stale confirmation.\n const row = await this.backend.getSession(meta.id);\n this.writeGuard.confirmHead(meta.id, row?.fHeadSequence ?? -1);\n }\n\n /**\n * 一次性清洗一个 session 的 surface/provenance 坐标到稠密空间并写回。\n *\n * 旧数据(rewind 前的代码写入)的 `sourceEventSeqs`、`surfaceOp` replace\n * range 与 compaction `shadowedRange` 是上游坐标,读取时每次都要做坐标\n * 解析对齐;清洗用与读取完全相同的解析(稠密优先 + 上游映射 + 剪枝 +\n * replace provenance 补全)把它们重写为稠密坐标,之后读取无需再对齐\n * (对已清洗数据解析恒为恒等)。torn tail 片段不参与(读取路径也不会\n * 保留它们)。返回实际变更的事件数。\n */\n async cleanseSession(id: SessionId, signal?: AbortSignal): Promise<{ changed: number }> {\n signal?.throwIfAborted();\n await this.ready;\n signal?.throwIfAborted();\n const eventRows = await this.backend.getEventRows(id);\n if (eventRows.length === 0) return { changed: 0 };\n const seqMap = buildSeqMap(eventRows);\n const denseTypeMap = new Map(eventRows.map((r) => [r.fSequence, r.fKind]));\n const { preserved } = scanRows(eventRows, 0, seqMap, denseTypeMap);\n normalizeSurfaceReplaceProvenance(preserved);\n const bySeq = new Map(preserved.map((event) => [event.seq, event]));\n const updates: Array<{\n fSequence: number;\n fSourceEventSeqs?: string | null;\n fSurfaceOp?: string | null;\n fData?: string;\n }> = [];\n for (const row of eventRows) {\n const event = bySeq.get(row.fSequence);\n if (event === undefined) continue; // torn tail fragment\n const surface = event as SessionEvent & { sourceEventSeqs?: number[]; surfaceOp?: unknown };\n const newSeqs =\n surface.sourceEventSeqs === undefined ? null : JSON.stringify(surface.sourceEventSeqs);\n const newOp = surface.surfaceOp === undefined ? null : JSON.stringify(surface.surfaceOp);\n const fields: {\n fSourceEventSeqs?: string | null;\n fSurfaceOp?: string | null;\n fData?: string;\n } = {};\n if (newSeqs !== row.fSourceEventSeqs) fields.fSourceEventSeqs = newSeqs;\n if (newOp !== row.fSurfaceOp) fields.fSurfaceOp = newOp;\n // fData 只在 compaction 计量事件的 shadowedRange 变化时重写(避免\n // JSON 重序列化带来的无关变更)。\n if (row.fKind === \"compaction/summary\" || row.fKind === \"compaction/prune\") {\n const storedData = JSON.parse(row.fData) as { shadowedRange?: unknown };\n const newData = event.data as unknown as { shadowedRange?: unknown };\n if (JSON.stringify(storedData.shadowedRange) !== JSON.stringify(newData.shadowedRange)) {\n fields.fData = JSON.stringify(event.data);\n }\n }\n if (Object.keys(fields).length > 0) {\n updates.push({ fSequence: row.fSequence, ...fields });\n }\n }\n if (updates.length === 0) return { changed: 0 };\n await this.backend.transaction(async (tx) => {\n for (const update of updates) {\n await tx.updateEventFields(id, update.fSequence, {\n ...(update.fSourceEventSeqs === undefined\n ? {}\n : { fSourceEventSeqs: update.fSourceEventSeqs }),\n ...(update.fSurfaceOp === undefined ? {} : { fSurfaceOp: update.fSurfaceOp }),\n ...(update.fData === undefined ? {} : { fData: update.fData }),\n });\n }\n });\n return { changed: updates.length };\n }\n\n /** List all materialized sessions' metadata (every row is a materialized session). */\n async list(signal?: AbortSignal): Promise<SessionHeader[]> {\n signal?.throwIfAborted();\n await this.ready;\n signal?.throwIfAborted();\n const rows = await this.backend.listSessions();\n signal?.throwIfAborted();\n return rows.map(rowToMeta);\n }\n\n /** List metadata with a source-qualified monotonic revision per session. */\n async listSnapshots(signal?: AbortSignal): Promise<SessionPersistenceSnapshot[]> {\n signal?.throwIfAborted();\n await this.ready;\n signal?.throwIfAborted();\n const rows = await this.backend.listSessions();\n signal?.throwIfAborted();\n return rows.map((row) => ({\n header: rowToMeta(row),\n revision: SessionPersistenceRevision(\n `${this.storeIdentity}:incarnation:${row.fIncarnation}:revision:${row.fRevision}`,\n ),\n }));\n }\n\n /** Close the database connection (awaited by the coordinator's dispose, post-drain). */\n async close(): Promise<void> {\n await this.ready;\n await this.backend.close();\n }\n\n /**\n * 同包分支 provider 的内部访问面(rewind / forkFrom 共享后端与写路径)。\n * @internal 仅供 `SessionBranchRdbProvider` 使用;不是公开 API。\n */\n internals(): SessionPersistenceRdbInternals {\n return {\n backend: this.backend,\n writeGuard: this.writeGuard,\n create: (meta) => this.create(meta),\n append: (id, events) => this.append(id, events),\n load: (id) => this.load(id),\n inspect: (id, signal) => this.inspect(id, signal),\n readFrom: (id, fromSeq, signal) => this.readFrom(id, fromSeq, signal),\n listSnapshots: (signal) => this.listSnapshots(signal),\n readStoredRevision: (id, signal) => this.readStoredRevision(id, signal),\n };\n }\n}\n\n/**\n * Build the configured backend. The PostgreSQL arm creates the `node-postgres`\n * pool here (its identity base comes from the parsed pool options); tests\n * inject a drizzle PG instance directly via {@link PostgresBackend}.\n */\nfunction createBackend(config: Config): Backend {\n if (config.type === \"sqlite\") {\n return new SqliteBackend({\n path: config.path,\n journalMode: config.journalMode ?? \"wal\",\n busyTimeout: config.busyTimeout ?? DEFAULT_BUSY_TIMEOUT_MS,\n });\n }\n const pool = new Pool({ connectionString: config.connectionString });\n // node-postgres 官方要求 Pool 必须监听 error:idle client 被服务器端\n // 切断(数据库重启、DROP DATABASE ... FORCE)时,未监听的 error 会以\n // uncaughtException 崩溃进程。池级错误会在下一次操作(query)处可见,\n // 这里只需消费事件,无需输出。\n pool.on(\"error\", () => {});\n const db = drizzlePg({ client: pool });\n const identityBase = [\n \"postgres\",\n pool.options.host ?? \"localhost\",\n String(pool.options.port ?? 5432),\n pool.options.database ?? \"\",\n ].join(\":\");\n return new PostgresBackend(db, { identityBase, close: () => pool.end() });\n}\n\n/**\n * Serialize an event's surface-metadata fields for SQL binding. Both fields are\n * nullable TEXT columns — null when the event has no surface metadata (non-surface\n * events, events written before surface support).\n *\n * `sourceEventSeqs` references events by UPSTREAM seq. Delta events dropped at\n * write time never get a persisted row, so a reference to one can never be\n * remapped on read — keeping it verbatim produces a `source >= current seq`\n * provenance violation when the log is replayed as a session seed. The write\n * path therefore prunes references through {@link WriteGuard.pruneRefs}; a\n * fully pruned list is stored as null (no provenance).\n * @param event - the event to serialize.\n * @param prune - prunes references to dropped deltas before binding. Defaults\n * to identity (e.g. repair closers, which never carry provenance).\n */\nfunction surfaceBindings(\n event: SessionEvent,\n prune: (refs: readonly number[]) => readonly number[] = (refs) => refs,\n): [string | null, string | null] {\n const se = event as SessionEvent<SurfaceEventType>;\n const sourceSeqs = se.sourceEventSeqs === undefined ? undefined : prune(se.sourceEventSeqs);\n return [\n sourceSeqs !== undefined && sourceSeqs.length > 0 ? JSON.stringify(sourceSeqs) : null,\n se.surfaceOp !== undefined ? JSON.stringify(se.surfaceOp) : null,\n ];\n}\n\n/**\n * Durably append one batch of persisted events to a session's tail inside the\n * enclosing transaction: mint each event's row (parent chain + playpen\n * dimensions + surface-metadata columns) and its bridge row, land both as ONE\n * multi-row INSERT each (N events are 2 statements instead of 2N), and return\n * the resulting head cursor.\n *\n * The anchor is the caller's responsibility: a normal append starts from the\n * head cursor (`head.fHeadEventId` / `head.fHeadSequence + 1`), while\n * crash-repair closers start from the ACTUAL tail row (the head cursor can lag\n * a hand-written torn tail). Both callers then persist the returned cursor via\n * {@link BackendTx.updateHead}.\n * @param tx - the enclosing transaction.\n * @param meta - the session being written (`meta.id` drives the bridge rows).\n * @param events - persisted events to append (callers already filtered out\n * ephemeral/ignorable events; non-empty).\n * @param anchor - the parent event id to chain from and the next dense seq.\n * @param prune - forwarded to {@link surfaceBindings}; defaults to identity\n * (repair closers never carry provenance).\n * @returns the new head cursor (last event id + its dense seq).\n */\nasync function appendEventTail(\n tx: BackendTx,\n meta: SessionHeader,\n events: readonly SessionEvent[],\n anchor: { parentId: string; nextSeq: number },\n prune: (refs: readonly number[]) => readonly number[] = (refs) => refs,\n): Promise<{ headEventId: string; headSequence: number }> {\n let parentId = anchor.parentId;\n let nextSeq = anchor.nextSeq;\n // Build both batches up front, then land them in ONE multi-row INSERT each:\n // N events are 2 statements instead of 2N (fewer SQLite statements and fewer\n // PostgreSQL round trips per commit).\n const eventRows: EventInsert[] = [];\n const bridgeRows: Array<{ fSessionId: SessionId; fEventId: string; fSequence: number }> = [];\n for (const event of events) {\n const eventId = randomUUID();\n const { role, name, actionId } = eventDimensions(event);\n const [surfaceSeqs, surfaceOp] = surfaceBindings(event, prune);\n eventRows.push({\n fEventId: eventId,\n fParentId: parentId,\n fKind: event.type,\n fRole: role,\n fName: name,\n fActionId: actionId,\n fEncoding: EVENT_ENCODING,\n fData: JSON.stringify(event.data),\n fCreatedAt: event.time,\n fOriginalSeq: event.seq,\n fSourceEventSeqs: surfaceSeqs,\n fSurfaceOp: surfaceOp,\n });\n bridgeRows.push({ fSessionId: meta.id, fEventId: eventId, fSequence: nextSeq });\n parentId = eventId;\n nextSeq++;\n }\n await tx.insertEvents(eventRows);\n await tx.insertBridges(bridgeRows);\n return { headEventId: parentId, headSequence: nextSeq - 1 };\n}\n\nexport default SessionPersistenceRdb;\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@morlay/session-rdb",
3
- "version": "0.0.10",
3
+ "version": "0.0.11",
4
4
  "description": "RDB durable session backend for DeepSeek Harness: implements both session-persistence and session-branch (rewind / retry / fork) providers.",
5
5
  "keywords": [
6
6
  "branch",
@@ -34,21 +34,22 @@
34
34
  "./cordis.patch.yml": "./cordis.patch.yml"
35
35
  },
36
36
  "dependencies": {
37
- "@morlay/session-branch": "^0.0.10",
37
+ "@morlay/session-branch": "^0.0.11",
38
38
  "drizzle-orm": "^1.0.0-rc.5-ab785fc",
39
39
  "pg": "^8.23.0"
40
40
  },
41
41
  "devDependencies": {
42
+ "@deepseek-ai/dsh-token-meter": "^0.1.1-rc.2",
42
43
  "@types/pg": "^8.23.1"
43
44
  },
44
45
  "peerDependencies": {
45
46
  "@deepseek-ai/cordis": "^4.0.1",
46
- "@deepseek-ai/dsh-invariants": "^0.1.0-rc.8",
47
- "@deepseek-ai/dsh-llm": "^0.1.0-rc.8",
48
- "@deepseek-ai/dsh-scope": "^0.1.0-rc.8",
49
- "@deepseek-ai/dsh-session": "^0.1.0-rc.8",
50
- "@deepseek-ai/dsh-session-persistence": "^0.1.0-rc.8",
51
- "@deepseek-ai/dsh-settings": "^0.1.0-rc.8",
47
+ "@deepseek-ai/dsh-invariants": "^0.1.1-rc.2",
48
+ "@deepseek-ai/dsh-llm": "^0.1.1-rc.2",
49
+ "@deepseek-ai/dsh-scope": "^0.1.1-rc.2",
50
+ "@deepseek-ai/dsh-session": "^0.1.1-rc.2",
51
+ "@deepseek-ai/dsh-session-persistence": "^0.1.1-rc.2",
52
+ "@deepseek-ai/dsh-settings": "^0.1.1-rc.2",
52
53
  "@deepseek-ai/schemastery": "^3.18.1"
53
54
  },
54
55
  "dsh": {