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

@@ -742,6 +742,12 @@ export class AuthorityGraphStore {
edges: emptyStatus.edges,
tombstones: emptyStatus.tombstones,
},
isEmptyCheck: {
empty: false,
nodes: emptyStatus.nodes,
edges: emptyStatus.edges,
tombstones: emptyStatus.tombstones,
},
migrationCompletedAt: 0,
migrationSource,
legacyRetentionUntil,
@@ -1032,12 +1038,42 @@ export class AuthorityGraphStore {
async _executeStatements(statements = []) {
const normalizedStatements = toArray(statements).filter((statement) => statement?.sql);
if (!normalizedStatements.length) return null;
const BATCH_SIZE = 150;
if (typeof this.sqlClient?.transaction === "function") {
return await this.sqlClient.transaction(normalizedStatements);
if (normalizedStatements.length <= BATCH_SIZE) {
return await this.sqlClient.transaction(normalizedStatements);
}
let lastResult = null;
for (let i = 0; i < normalizedStatements.length; i += BATCH_SIZE) {
const batch = normalizedStatements.slice(i, i + BATCH_SIZE);
lastResult = await this.sqlClient.transaction(batch);
}
return lastResult;
}
const upsertStatements = [];
const deleteStatements = [];
for (const stmt of normalizedStatements) {
if (stmt.sql.trim().toUpperCase().startsWith("DELETE")) {
deleteStatements.push(stmt);
} else {
upsertStatements.push(stmt);
}
}
let result = null;
for (const statement of normalizedStatements) {
result = await this._execute(statement.sql, statement.params || {});
for (const stmt of upsertStatements) {
result = await this._execute(stmt.sql, stmt.params || {});
}
for (const stmt of deleteStatements) {
result = await this._execute(stmt.sql, stmt.params || {});
}
if (deleteStatements.length > 0 && upsertStatements.length > 0) {
console.warn("[ST-BME] _executeStatements fallback 路径执行:先 upsert 后 delete无事务保护", {
chatId: this.chatId,
upsertCount: upsertStatements.length,
deleteCount: deleteStatements.length,
});
}
return result;
}