fix(migration): 7 critical fixes for Authority migration safety and data integrity

- Fix #2: _executeStatements fallback now batches transactions (150/batch)
  and reorders upsert-before-delete to prevent data loss on payload overflow
- Fix #3: Read/write migratedToAuthority marker in chat_metadata to prevent
  re-overwriting from legacy sources after Authority migration
- Fix #1: Add OPFS → Authority migration channel (exportOpfsSnapshotForChat,
  maybeImportLegacyOpfsSnapshotToLocalStore) inserted before IndexedDB in
  migration chain
- Fix #4: Mark runtimeVectorIndexState dirty with triviumRebuildRequired
  and trigger submitAuthorityVectorRebuildJob after all three migration paths
- Fix #5: Dual-save safety snapshots to Authority blob; rollback can now
  recover from blob when local IndexedDB snapshot is unavailable
- Fix #6: Add isEmptyCheck detail and console.warn for near-empty stores
  to help diagnose residual vs real data in store-not-empty skips
- Fix #7: Add overflow warning logs and sessionStorage persistence for
  Authority offline queue max-items/max-bytes exceeded events
This commit is contained in:
Youzini-afk
2026-04-29 01:15:37 +08:00
parent 79fbd369ba
commit d702e267d3
5 changed files with 574 additions and 11 deletions

View File

@@ -1156,6 +1156,50 @@ export async function rollbackFromRestoreSafetySnapshot(chatId, options = {}) {
try {
const status = await getRestoreSafetySnapshotStatus(normalizedChatId, options);
if (!status.exists) {
try {
const blobAdapter = getAuthorityBlobAdapter(options);
if (blobAdapter && typeof blobAdapter.readJson === "function") {
const blobSnapshot = await blobAdapter.readJson(
`ST-BME/migration-safety/${normalizedChatId}.json`,
{ namespace: "st-bme-safety" },
);
if (blobSnapshot && typeof blobSnapshot === "object" && !Array.isArray(blobSnapshot)) {
const snapshot = normalizeSyncSnapshot(blobSnapshot, normalizedChatId);
const hasNodes = Array.isArray(snapshot.nodes) && snapshot.nodes.length > 0;
const hasEdges = Array.isArray(snapshot.edges) && snapshot.edges.length > 0;
if (hasNodes || hasEdges) {
const db = await getDb(normalizedChatId, options);
await db.importSnapshot(snapshot, {
mode: "replace",
preserveRevision: true,
revision: normalizeRevision(snapshot.meta?.revision),
markSyncDirty: false,
});
await patchDbMeta(db, {
deviceId: getOrCreateDeviceId(),
syncDirty: true,
syncDirtyReason: "restore-safety-rollback-from-blob",
lastBackupRollbackAt: Date.now(),
});
await invokeSyncAppliedHook(options, {
chatId: normalizedChatId,
action: "restore-backup-from-blob",
revision: normalizeRevision(snapshot.meta?.revision),
});
return {
restored: true,
chatId: normalizedChatId,
revision: normalizeRevision(snapshot.meta?.revision),
createdAt: 0,
blobRestored: true,
reason: "restored-from-authority-blob",
};
}
}
}
} catch (blobError) {
console.warn("[ST-BME] 从 Authority blob 恢复安全快照失败:", blobError);
}
return {
restored: false,
chatId: normalizedChatId,