fix: preserve history frontier in opfs delta commits

This commit is contained in:
Youzini-afk
2026-04-17 20:17:25 +08:00
parent aef6d1dca3
commit a35237ae6d
2 changed files with 173 additions and 10 deletions

View File

@@ -205,18 +205,47 @@ function normalizePersistSnapshotView(snapshot = {}) {
};
}
const hasExplicitMeta =
snapshot.meta &&
typeof snapshot.meta === "object" &&
!Array.isArray(snapshot.meta);
const hasExplicitState =
snapshot.state &&
typeof snapshot.state === "object" &&
!Array.isArray(snapshot.state);
const shouldDeriveFromRuntimeGraph =
!hasExplicitMeta &&
!hasExplicitState &&
(Object.prototype.hasOwnProperty.call(snapshot, "historyState") ||
Object.prototype.hasOwnProperty.call(snapshot, "vectorIndexState") ||
Object.prototype.hasOwnProperty.call(snapshot, "batchJournal") ||
Object.prototype.hasOwnProperty.call(snapshot, "maintenanceJournal") ||
Object.prototype.hasOwnProperty.call(snapshot, "summaryState") ||
Object.prototype.hasOwnProperty.call(snapshot, "knowledgeState") ||
Object.prototype.hasOwnProperty.call(snapshot, "regionState") ||
Object.prototype.hasOwnProperty.call(snapshot, "timelineState") ||
Object.prototype.hasOwnProperty.call(snapshot, "lastRecallResult") ||
Object.prototype.hasOwnProperty.call(snapshot, "lastProcessedSeq"));
const derivedRuntimeSnapshot = shouldDeriveFromRuntimeGraph
? buildSnapshotFromGraph(snapshot, {
chatId: normalizeChatId(snapshot?.historyState?.chatId || ""),
})
: null;
return {
meta:
snapshot.meta &&
typeof snapshot.meta === "object" &&
!Array.isArray(snapshot.meta)
? snapshot.meta
meta: hasExplicitMeta
? snapshot.meta
: derivedRuntimeSnapshot?.meta &&
typeof derivedRuntimeSnapshot.meta === "object" &&
!Array.isArray(derivedRuntimeSnapshot.meta)
? derivedRuntimeSnapshot.meta
: {},
state:
snapshot.state &&
typeof snapshot.state === "object" &&
!Array.isArray(snapshot.state)
? snapshot.state
state: hasExplicitState
? snapshot.state
: derivedRuntimeSnapshot?.state &&
typeof derivedRuntimeSnapshot.state === "object" &&
!Array.isArray(derivedRuntimeSnapshot.state)
? derivedRuntimeSnapshot.state
: {},
nodes: toArray(snapshot.nodes),
edges: toArray(snapshot.edges),

View File

@@ -1,11 +1,80 @@
import assert from "node:assert/strict";
import { buildPersistDelta } from "../sync/bme-db.js";
import {
BME_GRAPH_LOCAL_STORAGE_MODE_OPFS_PRIMARY,
OpfsGraphStore,
} from "../sync/bme-opfs-store.js";
import { createMemoryOpfsRoot } from "./helpers/memory-opfs.mjs";
function createRuntimeGraphLikeSnapshot({
chatId,
lastProcessedFloor,
extractionCount,
nodes = [],
edges = [],
batchJournal = [],
} = {}) {
return {
version: 9,
lastProcessedSeq: lastProcessedFloor,
nodes,
edges,
historyState: {
chatId,
lastProcessedAssistantFloor: lastProcessedFloor,
extractionCount,
processedMessageHashVersion: 2,
processedMessageHashes: {},
processedMessageHashesNeedRefresh: false,
historyDirtyFrom: null,
lastMutationReason: "",
lastMutationSource: "",
lastRecoveryResult: null,
lastBatchStatus: null,
lastExtractedRegion: "",
activeRegion: "",
activeRegionSource: "",
activeStorySegmentId: "",
activeStoryTimeLabel: "",
activeStoryTimeSource: "",
lastExtractedStorySegmentId: "",
activeCharacterPovOwner: "",
activeUserPovOwner: "",
activeRecallOwnerKey: "",
recentRecallOwnerKeys: [],
},
vectorIndexState: {
mode: "backend",
collectionId: `st-bme::${chatId}`,
source: "",
modelScope: "",
hashToNodeId: {},
nodeToHash: {},
dirty: false,
replayRequiredNodeIds: [],
dirtyReason: "",
pendingRepairFromFloor: null,
lastSyncAt: 0,
lastStats: {
total: 0,
indexed: 0,
stale: 0,
pending: 0,
},
lastWarning: "",
lastIntegrityIssue: null,
},
batchJournal,
maintenanceJournal: [],
knowledgeState: {},
regionState: {},
timelineState: {},
summaryState: {},
lastRecallResult: null,
};
}
async function testCommitDeltaAndPatchMetaSerialize() {
const rootDirectory = createMemoryOpfsRoot({
writeDelayMs: 5,
@@ -128,6 +197,71 @@ async function testImportSnapshotAndClearAllSerialize() {
assert.equal(snapshot.state.extractionCount, 4);
}
async function testGraphLikeDeltaPreservesHistoryFrontier() {
const rootDirectory = createMemoryOpfsRoot();
const store = new OpfsGraphStore("chat-opfs-graph-like-delta", {
rootDirectoryFactory: async () => rootDirectory,
storeMode: BME_GRAPH_LOCAL_STORAGE_MODE_OPFS_PRIMARY,
});
await store.open();
const beforeGraph = createRuntimeGraphLikeSnapshot({
chatId: "chat-opfs-graph-like-delta",
lastProcessedFloor: -1,
extractionCount: 0,
});
const afterGraph = createRuntimeGraphLikeSnapshot({
chatId: "chat-opfs-graph-like-delta",
lastProcessedFloor: 12,
extractionCount: 3,
nodes: [
{
id: "node-graph-like",
type: "event",
fields: { title: "graph-like" },
archived: false,
updatedAt: 12,
},
],
batchJournal: [
{
id: "journal-1",
processedRange: [12, 12],
},
],
});
const delta = buildPersistDelta(beforeGraph, afterGraph, {
useNativeDelta: false,
});
assert.equal(delta.runtimeMetaPatch.lastProcessedFloor, 12);
assert.equal(delta.runtimeMetaPatch.extractionCount, 3);
assert.equal(
delta.runtimeMetaPatch.runtimeHistoryState?.lastProcessedAssistantFloor,
12,
);
await store.commitDelta(delta, {
reason: "graph-like-delta",
requestedRevision: 1,
markSyncDirty: true,
});
const reopenedStore = new OpfsGraphStore("chat-opfs-graph-like-delta", {
rootDirectoryFactory: async () => rootDirectory,
storeMode: BME_GRAPH_LOCAL_STORAGE_MODE_OPFS_PRIMARY,
});
await reopenedStore.open();
const snapshot = await reopenedStore.exportSnapshot();
assert.equal(snapshot.state.lastProcessedFloor, 12);
assert.equal(snapshot.state.extractionCount, 3);
assert.equal(
snapshot.meta.runtimeHistoryState?.lastProcessedAssistantFloor,
12,
);
}
await testCommitDeltaAndPatchMetaSerialize();
await testImportSnapshotAndClearAllSerialize();
await testGraphLikeDeltaPreservesHistoryFrontier();
console.log("opfs-write-serialization tests passed");