mirror of
https://github.com/Youzini-afk/ST-Bionic-Memory-Ecology.git
synced 2026-05-15 22:30:38 +08:00
fix: clear stale accepted commit marker after deleting local IndexedDB caches
When users delete local BME IndexedDB via UI actions (delete current/all IDB), the chat metadata's st_bme_commit_marker was not cleared. This left an accepted high-revision promise with no local DB backing, causing persist-mismatch:indexeddb-behind-commit-marker and blocking graph load indefinitely. - index.js: add clearCurrentChatCommitMarker() helper and expose via runtime - ui-actions-controller.js: call clearCurrentChatCommitMarker before syncGraphLoadFromLiveContext after IDB deletion - p0-regressions.mjs: regression test asserting marker is cleared before reload after current-IDB deletion
This commit is contained in:
37
index.js
37
index.js
@@ -320,6 +320,42 @@ function syncCommitMarkerToPersistenceState(context = getContext()) {
|
||||
return marker;
|
||||
}
|
||||
|
||||
function clearCurrentChatCommitMarker(
|
||||
{
|
||||
context = getContext(),
|
||||
reason = "manual-clear-commit-marker",
|
||||
immediate = true,
|
||||
} = {},
|
||||
) {
|
||||
if (!context) {
|
||||
return {
|
||||
cleared: false,
|
||||
reason: "missing-context",
|
||||
saveMode: "",
|
||||
marker: null,
|
||||
};
|
||||
}
|
||||
|
||||
const marker = getChatCommitMarker(context);
|
||||
writeChatMetadataPatch(context, {
|
||||
[GRAPH_COMMIT_MARKER_KEY]: null,
|
||||
});
|
||||
const saveMode = triggerChatMetadataSave(context, { immediate });
|
||||
updateGraphPersistenceState({
|
||||
commitMarker: null,
|
||||
persistMismatchReason: "",
|
||||
lastPersistReason: String(reason || "manual-clear-commit-marker"),
|
||||
lastPersistMode: `commit-marker-clear:${saveMode}`,
|
||||
});
|
||||
|
||||
return {
|
||||
cleared: Boolean(marker),
|
||||
reason: String(reason || "manual-clear-commit-marker"),
|
||||
saveMode,
|
||||
marker: cloneRuntimeDebugValue(marker, null),
|
||||
};
|
||||
}
|
||||
|
||||
function isAcceptedPersistTier(storageTier = "none") {
|
||||
const normalizedTier = String(storageTier || "none").trim().toLowerCase();
|
||||
return normalizedTier === "indexeddb" || normalizedTier === "chat-state";
|
||||
@@ -13700,6 +13736,7 @@ const _cleanupRuntime = () => ({
|
||||
},
|
||||
clearCachedIndexedDbSnapshot,
|
||||
clearAllCachedIndexedDbSnapshots,
|
||||
clearCurrentChatCommitMarker,
|
||||
deleteRemoteSyncFile: (chatId) => deleteRemoteSyncFile(chatId, {
|
||||
fetch: globalThis.fetch?.bind(globalThis),
|
||||
getRequestHeaders: typeof getRequestHeaders === "function" ? getRequestHeaders : undefined,
|
||||
|
||||
@@ -67,6 +67,7 @@ import {
|
||||
shouldRunRecallForTransaction,
|
||||
} from "../ui/ui-status.js";
|
||||
import {
|
||||
onDeleteCurrentIdbController,
|
||||
onManualCompressController,
|
||||
onManualEvolveController,
|
||||
onManualSleepController,
|
||||
@@ -2036,6 +2037,93 @@ async function testBackendVectorQueryFailureMarksStateDirty() {
|
||||
}
|
||||
}
|
||||
|
||||
async function testDeleteCurrentIdbClearsCommitMarkerBeforeReload() {
|
||||
const originalIndexedDb = globalThis.indexedDB;
|
||||
const callLog = [];
|
||||
globalThis.indexedDB = {
|
||||
deleteDatabase(name) {
|
||||
callLog.push(["delete-db", String(name || "")]);
|
||||
const request = {
|
||||
onsuccess: null,
|
||||
onerror: null,
|
||||
onblocked: null,
|
||||
};
|
||||
queueMicrotask(() => {
|
||||
request.onsuccess?.();
|
||||
});
|
||||
return request;
|
||||
},
|
||||
};
|
||||
|
||||
try {
|
||||
const runtime = {
|
||||
confirm() {
|
||||
return true;
|
||||
},
|
||||
getCurrentChatId() {
|
||||
return "chat-delete-idb";
|
||||
},
|
||||
buildBmeDbName(chatId) {
|
||||
return `STBME_${chatId}`;
|
||||
},
|
||||
buildRestoreSafetyDbName(chatId) {
|
||||
return `STBME___restore__${chatId}`;
|
||||
},
|
||||
async closeBmeDb(chatId) {
|
||||
callLog.push(["close-db", chatId]);
|
||||
},
|
||||
clearCachedIndexedDbSnapshot(chatId) {
|
||||
callLog.push(["clear-indexeddb-cache", chatId]);
|
||||
},
|
||||
clearCurrentChatCommitMarker(options = {}) {
|
||||
callLog.push([
|
||||
"clear-commit-marker",
|
||||
String(options.reason || ""),
|
||||
options.immediate === true,
|
||||
]);
|
||||
},
|
||||
syncGraphLoadFromLiveContext(options = {}) {
|
||||
callLog.push([
|
||||
"sync-graph-load",
|
||||
String(options.source || ""),
|
||||
options.force === true,
|
||||
]);
|
||||
},
|
||||
refreshPanelLiveState() {
|
||||
callLog.push(["refresh-panel"]);
|
||||
},
|
||||
toastr: {
|
||||
success(message) {
|
||||
callLog.push(["toast-success", String(message || "")]);
|
||||
},
|
||||
warning(message) {
|
||||
callLog.push(["toast-warning", String(message || "")]);
|
||||
},
|
||||
error(message) {
|
||||
callLog.push(["toast-error", String(message || "")]);
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const result = await onDeleteCurrentIdbController(runtime);
|
||||
assert.equal(result?.handledToast, true);
|
||||
const clearMarkerIndex = callLog.findIndex(
|
||||
(entry) => entry[0] === "clear-commit-marker",
|
||||
);
|
||||
const syncLoadIndex = callLog.findIndex(
|
||||
(entry) => entry[0] === "sync-graph-load",
|
||||
);
|
||||
assert.ok(clearMarkerIndex >= 0, "删除当前 IDB 后应清理 commit marker");
|
||||
assert.ok(syncLoadIndex >= 0, "删除当前 IDB 后应重新同步图谱加载状态");
|
||||
assert.ok(
|
||||
clearMarkerIndex < syncLoadIndex,
|
||||
"应先清理 commit marker,再触发图谱重探测",
|
||||
);
|
||||
} finally {
|
||||
globalThis.indexedDB = originalIndexedDb;
|
||||
}
|
||||
}
|
||||
|
||||
async function testCompressTypeAcceptsTopLevelFieldsResult() {
|
||||
const graph = createEmptyGraph();
|
||||
const typeDef = {
|
||||
@@ -6778,6 +6866,7 @@ async function testManualSleepExplainsThatItIsLocalOnlyWhenNothingChanges() {
|
||||
await testCompressorMigratesEdgesToCompressedNode();
|
||||
await testVectorIndexKeepsDirtyOnDirectPartialEmbeddingFailure();
|
||||
await testBackendVectorQueryFailureMarksStateDirty();
|
||||
await testDeleteCurrentIdbClearsCommitMarkerBeforeReload();
|
||||
await testCompressTypeAcceptsTopLevelFieldsResult();
|
||||
await testExtractorFailsOnUnknownOperation();
|
||||
await testExtractorNormalizesFlatCreateOperation();
|
||||
|
||||
@@ -1193,6 +1193,10 @@ export async function onDeleteCurrentIdbController(runtime) {
|
||||
});
|
||||
}
|
||||
runtime.clearCachedIndexedDbSnapshot?.(chatId);
|
||||
runtime.clearCurrentChatCommitMarker?.({
|
||||
reason: "manual-delete-current-idb",
|
||||
immediate: true,
|
||||
});
|
||||
runtime.syncGraphLoadFromLiveContext?.({
|
||||
source: "manual-delete-current-idb",
|
||||
force: true,
|
||||
@@ -1245,6 +1249,10 @@ export async function onDeleteAllIdbController(runtime) {
|
||||
runtime.clearAllCachedIndexedDbSnapshots?.();
|
||||
const activeChatId = runtime.getCurrentChatId?.();
|
||||
if (activeChatId) {
|
||||
runtime.clearCurrentChatCommitMarker?.({
|
||||
reason: "manual-delete-all-idb",
|
||||
immediate: true,
|
||||
});
|
||||
runtime.syncGraphLoadFromLiveContext?.({
|
||||
source: "manual-delete-all-idb",
|
||||
force: true,
|
||||
|
||||
Reference in New Issue
Block a user