mirror of
https://github.com/Youzini-afk/ST-Bionic-Memory-Ecology.git
synced 2026-06-14 02:40:45 +08:00
Refactor extraction persistence into two-stage status model
This commit is contained in:
155
tests/extraction-persistence-gating.mjs
Normal file
155
tests/extraction-persistence-gating.mjs
Normal file
@@ -0,0 +1,155 @@
|
||||
import assert from "node:assert/strict";
|
||||
|
||||
import { executeExtractionBatchController } from "../maintenance/extraction-controller.js";
|
||||
import {
|
||||
createBatchStatusSkeleton,
|
||||
finalizeBatchStatus,
|
||||
setBatchStageOutcome,
|
||||
} from "../ui/ui-status.js";
|
||||
|
||||
function createRuntime(persistResult) {
|
||||
const graph = {
|
||||
nodes: [],
|
||||
edges: [],
|
||||
historyState: {},
|
||||
};
|
||||
let processedHistoryUpdates = 0;
|
||||
|
||||
return {
|
||||
graph,
|
||||
processedHistoryUpdates,
|
||||
ensureCurrentGraphRuntimeState() {},
|
||||
throwIfAborted() {},
|
||||
getCurrentGraph() {
|
||||
return graph;
|
||||
},
|
||||
getLastProcessedAssistantFloor() {
|
||||
return 4;
|
||||
},
|
||||
getExtractionCount() {
|
||||
return 6;
|
||||
},
|
||||
cloneGraphSnapshot(value) {
|
||||
return JSON.parse(JSON.stringify(value));
|
||||
},
|
||||
buildExtractionMessages() {
|
||||
return [{ seq: 5, role: "assistant", content: "测试消息" }];
|
||||
},
|
||||
createBatchStatusSkeleton,
|
||||
async extractMemories() {
|
||||
return {
|
||||
success: true,
|
||||
newNodes: 1,
|
||||
updatedNodes: 0,
|
||||
newEdges: 0,
|
||||
newNodeIds: ["node-1"],
|
||||
processedRange: [5, 5],
|
||||
};
|
||||
},
|
||||
getSchema() {
|
||||
return [];
|
||||
},
|
||||
getEmbeddingConfig() {
|
||||
return null;
|
||||
},
|
||||
setLastExtractionStatus() {},
|
||||
setBatchStageOutcome,
|
||||
async handleExtractionSuccess(result, _endIdx, _settings, _signal, batchStatus) {
|
||||
setBatchStageOutcome(batchStatus, "finalize", "success");
|
||||
return {
|
||||
postProcessArtifacts: [],
|
||||
vectorHashesInserted: [],
|
||||
warnings: [],
|
||||
batchStatus,
|
||||
};
|
||||
},
|
||||
async persistExtractionBatchResult() {
|
||||
return persistResult;
|
||||
},
|
||||
finalizeBatchStatus,
|
||||
shouldAdvanceProcessedHistory(batchStatus) {
|
||||
return batchStatus.historyAdvanceAllowed === true;
|
||||
},
|
||||
updateProcessedHistorySnapshot() {
|
||||
processedHistoryUpdates += 1;
|
||||
},
|
||||
appendBatchJournal() {},
|
||||
createBatchJournalEntry() {
|
||||
return { id: "journal-1" };
|
||||
},
|
||||
computePostProcessArtifacts() {
|
||||
return [];
|
||||
},
|
||||
getGraphPersistenceState() {
|
||||
return { chatId: "chat-test" };
|
||||
},
|
||||
console,
|
||||
get processedHistoryUpdates() {
|
||||
return processedHistoryUpdates;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
{
|
||||
const runtime = createRuntime({
|
||||
saved: false,
|
||||
queued: true,
|
||||
blocked: true,
|
||||
accepted: false,
|
||||
reason: "persist-queued",
|
||||
revision: 7,
|
||||
saveMode: "immediate",
|
||||
storageTier: "none",
|
||||
});
|
||||
const result = await executeExtractionBatchController(runtime, {
|
||||
chat: [{ is_user: false, mes: "测试" }],
|
||||
startIdx: 5,
|
||||
endIdx: 5,
|
||||
settings: {},
|
||||
});
|
||||
|
||||
assert.equal(result.success, true);
|
||||
assert.equal(result.historyAdvanceAllowed, false);
|
||||
assert.equal(runtime.processedHistoryUpdates, 0);
|
||||
assert.equal(
|
||||
runtime.graph.historyState.lastBatchStatus.persistence.outcome,
|
||||
"queued",
|
||||
);
|
||||
assert.equal(
|
||||
runtime.graph.historyState.lastBatchStatus.historyAdvanceAllowed,
|
||||
false,
|
||||
);
|
||||
}
|
||||
|
||||
{
|
||||
const runtime = createRuntime({
|
||||
saved: true,
|
||||
queued: false,
|
||||
blocked: false,
|
||||
accepted: true,
|
||||
reason: "indexeddb",
|
||||
revision: 8,
|
||||
saveMode: "indexeddb",
|
||||
storageTier: "indexeddb",
|
||||
});
|
||||
const result = await executeExtractionBatchController(runtime, {
|
||||
chat: [{ is_user: false, mes: "测试" }],
|
||||
startIdx: 5,
|
||||
endIdx: 5,
|
||||
settings: {},
|
||||
});
|
||||
|
||||
assert.equal(result.success, true);
|
||||
assert.equal(result.historyAdvanceAllowed, true);
|
||||
assert.equal(runtime.processedHistoryUpdates, 1);
|
||||
assert.equal(
|
||||
runtime.graph.historyState.lastBatchStatus.persistence.outcome,
|
||||
"saved",
|
||||
);
|
||||
assert.equal(
|
||||
runtime.graph.historyState.lastBatchStatus.historyAdvanceAllowed,
|
||||
true,
|
||||
);
|
||||
}
|
||||
|
||||
console.log("extraction-persistence-gating tests passed");
|
||||
@@ -11,12 +11,16 @@ import {
|
||||
} from "../sync/bme-db.js";
|
||||
import { onMessageReceivedController } from "../host/event-binding.js";
|
||||
import {
|
||||
buildGraphCommitMarker,
|
||||
detectIndexedDbSnapshotCommitMarkerMismatch,
|
||||
cloneGraphForPersistence,
|
||||
cloneRuntimeDebugValue,
|
||||
findGraphShadowSnapshotByIntegrity,
|
||||
getAcceptedCommitMarkerRevision,
|
||||
getGraphPersistedRevision,
|
||||
getGraphIdentityAliasCandidates,
|
||||
getGraphPersistenceMeta,
|
||||
GRAPH_COMMIT_MARKER_KEY,
|
||||
getGraphShadowSnapshotStorageKey,
|
||||
GRAPH_LOAD_PENDING_CHAT_ID,
|
||||
GRAPH_IDENTITY_ALIAS_STORAGE_KEY,
|
||||
@@ -27,6 +31,8 @@ import {
|
||||
GRAPH_SHADOW_SNAPSHOT_STORAGE_PREFIX,
|
||||
GRAPH_STARTUP_RECONCILE_DELAYS_MS,
|
||||
MODULE_NAME,
|
||||
normalizeGraphCommitMarker,
|
||||
readGraphCommitMarker,
|
||||
readGraphShadowSnapshot,
|
||||
rememberGraphIdentityAlias,
|
||||
removeGraphShadowSnapshot,
|
||||
@@ -384,11 +390,15 @@ async function createGraphPersistenceHarness({
|
||||
formatRecallContextLine,
|
||||
readPersistedRecallFromUserMessage,
|
||||
cloneGraphForPersistence,
|
||||
buildGraphCommitMarker,
|
||||
cloneRuntimeDebugValue,
|
||||
detectIndexedDbSnapshotCommitMarkerMismatch,
|
||||
onMessageReceivedController,
|
||||
getAcceptedCommitMarkerRevision,
|
||||
getGraphPersistenceMeta,
|
||||
getGraphPersistedRevision,
|
||||
getGraphIdentityAliasCandidates,
|
||||
GRAPH_COMMIT_MARKER_KEY,
|
||||
getGraphShadowSnapshotStorageKey,
|
||||
GRAPH_IDENTITY_ALIAS_STORAGE_KEY,
|
||||
GRAPH_LOAD_PENDING_CHAT_ID,
|
||||
@@ -400,6 +410,8 @@ async function createGraphPersistenceHarness({
|
||||
GRAPH_STARTUP_RECONCILE_DELAYS_MS,
|
||||
MODULE_NAME,
|
||||
findGraphShadowSnapshotByIntegrity,
|
||||
normalizeGraphCommitMarker,
|
||||
readGraphCommitMarker,
|
||||
readGraphShadowSnapshot,
|
||||
rememberGraphIdentityAlias,
|
||||
removeGraphShadowSnapshot,
|
||||
@@ -1221,8 +1233,8 @@ result = {
|
||||
reason: "blocked-save",
|
||||
markMutation: false,
|
||||
});
|
||||
assert.equal(result.saved, true);
|
||||
assert.equal(result.queued, false);
|
||||
assert.equal(result.saved, false);
|
||||
assert.equal(result.queued, true);
|
||||
assert.equal(result.blocked, false);
|
||||
assert.equal(result.saveMode, "indexeddb-queued");
|
||||
assert.equal(harness.runtimeContext.__chatContext.chatMetadata, undefined);
|
||||
@@ -1941,7 +1953,8 @@ result = {
|
||||
reason: "first-meaningful-graph",
|
||||
});
|
||||
|
||||
assert.equal(result.saved, true);
|
||||
assert.equal(result.saved, false);
|
||||
assert.equal(result.queued, true);
|
||||
assert.equal(result.saveMode, "indexeddb-queued");
|
||||
assert.equal(harness.runtimeContext.__contextImmediateSaveCalls, 0);
|
||||
assert.equal(harness.runtimeContext.__contextSaveCalls, 0);
|
||||
|
||||
91
tests/persistence-commit-marker.mjs
Normal file
91
tests/persistence-commit-marker.mjs
Normal file
@@ -0,0 +1,91 @@
|
||||
import assert from "node:assert/strict";
|
||||
|
||||
import {
|
||||
buildGraphCommitMarker,
|
||||
detectIndexedDbSnapshotCommitMarkerMismatch,
|
||||
getAcceptedCommitMarkerRevision,
|
||||
GRAPH_COMMIT_MARKER_KEY,
|
||||
normalizeGraphCommitMarker,
|
||||
readGraphCommitMarker,
|
||||
writeChatMetadataPatch,
|
||||
} from "../graph/graph-persistence.js";
|
||||
import { addNode, createEmptyGraph, createNode } from "../graph/graph.js";
|
||||
|
||||
const graph = createEmptyGraph();
|
||||
graph.historyState.chatId = "chat-marker";
|
||||
graph.historyState.lastProcessedAssistantFloor = 10;
|
||||
graph.historyState.extractionCount = 4;
|
||||
addNode(
|
||||
graph,
|
||||
createNode({
|
||||
type: "event",
|
||||
fields: { title: "事件A", summary: "测试事件" },
|
||||
seq: 10,
|
||||
}),
|
||||
);
|
||||
|
||||
const marker = buildGraphCommitMarker(graph, {
|
||||
revision: 12,
|
||||
storageTier: "indexeddb",
|
||||
accepted: true,
|
||||
reason: "unit-test",
|
||||
});
|
||||
assert.equal(marker.revision, 12);
|
||||
assert.equal(marker.accepted, true);
|
||||
assert.equal(marker.lastProcessedAssistantFloor, 10);
|
||||
assert.equal(marker.extractionCount, 4);
|
||||
assert.equal(marker.nodeCount, 1);
|
||||
assert.equal(marker.edgeCount, 0);
|
||||
assert.equal(marker.archivedCount, 0);
|
||||
assert.equal(getAcceptedCommitMarkerRevision(marker), 12);
|
||||
|
||||
const normalized = normalizeGraphCommitMarker({
|
||||
revision: "15",
|
||||
lastProcessedAssistantFloor: "18",
|
||||
extractionCount: "6",
|
||||
nodeCount: "9",
|
||||
edgeCount: "3",
|
||||
archivedCount: "2",
|
||||
storageTier: "shadow",
|
||||
accepted: true,
|
||||
reason: "normalized",
|
||||
});
|
||||
assert.equal(normalized.revision, 15);
|
||||
assert.equal(normalized.lastProcessedAssistantFloor, 18);
|
||||
assert.equal(normalized.storageTier, "shadow");
|
||||
|
||||
const context = {
|
||||
chatMetadata: {},
|
||||
};
|
||||
writeChatMetadataPatch(context, {
|
||||
[GRAPH_COMMIT_MARKER_KEY]: marker,
|
||||
});
|
||||
assert.deepEqual(readGraphCommitMarker(context), marker);
|
||||
|
||||
const mismatch = detectIndexedDbSnapshotCommitMarkerMismatch(
|
||||
{
|
||||
meta: {
|
||||
revision: 9,
|
||||
},
|
||||
},
|
||||
marker,
|
||||
);
|
||||
assert.equal(mismatch.mismatched, true);
|
||||
assert.equal(
|
||||
mismatch.reason,
|
||||
"persist-mismatch:indexeddb-behind-commit-marker",
|
||||
);
|
||||
assert.equal(mismatch.markerRevision, 12);
|
||||
assert.equal(mismatch.snapshotRevision, 9);
|
||||
|
||||
const noMismatch = detectIndexedDbSnapshotCommitMarkerMismatch(
|
||||
{
|
||||
meta: {
|
||||
revision: 12,
|
||||
},
|
||||
},
|
||||
marker,
|
||||
);
|
||||
assert.equal(noMismatch.mismatched, false);
|
||||
|
||||
console.log("persistence-commit-marker tests passed");
|
||||
Reference in New Issue
Block a user