{"record":{"id":"e863e5f3bfbf1ccb","repo":"toeverything/AFFiNE","slug":"doc-not-found","errorCode":"doc_not_found","errorMessage":"Doc ${docId} under Space ${spaceId} not found.","messagePattern":"Doc (.+?) under Space (.+?) not found\\.","errorType":"exception","errorClass":"DocNotFound","httpStatus":404,"severity":"error","filePath":"packages/backend/server/src/core/doc/adapters/workspace.ts","lineNumber":213,"sourceCode":"    };\n  }\n\n  override async rollbackDoc(\n    spaceId: string,\n    docId: string,\n    timestamp: number,\n    editorId?: string\n  ): Promise<void> {\n    await using _lock = await this.lockDocForUpdate(spaceId, docId);\n    const toSnapshot = await this.getDocHistory(spaceId, docId, timestamp);\n    if (!toSnapshot) {\n      throw new DocHistoryNotFound({ spaceId, docId, timestamp });\n    }\n\n    const fromSnapshot = await this.getDocSnapshot(spaceId, docId);\n\n    if (!fromSnapshot) {\n      throw new DocNotFound({ spaceId, docId });\n    }\n\n    // force create a new history record after rollback\n    await this.createDocHistory(\n      {\n        ...fromSnapshot,\n        // override the editor to the one who requested the rollback\n        editor: editorId,\n      },\n      true\n    );\n    // WARN:\n    //  we should never do the snapshot updating in recovering,\n    //  which is not the solution in CRDT.\n    //  let user revert in client and update the data in sync system\n    //    const change = this.generateChangeUpdate(fromSnapshot.bin, toSnapshot.bin);\n    //    await this.pushDocUpdates(spaceId, docId, [change]);\n","sourceCodeStart":195,"sourceCodeEnd":231,"githubUrl":"https://github.com/toeverything/AFFiNE/blob/26c515e050211269e911f7d9cfe162a26c83ed98/packages/backend/server/src/core/doc/adapters/workspace.ts#L195-L231","documentation":"Thrown by `rollbackDoc` after the target history was found, when the *current* snapshot `getDocSnapshot(spaceId, docId)` returns null. Without a 'from' snapshot the rollback cannot seed the post-rollback history record, so it aborts with `DocNotFound` (category `resource_not_found`) interpolating `docId` and `spaceId`.","triggerScenarios":"Rolling back a doc whose current snapshot row is missing even though history rows exist — e.g., the live snapshot was deleted/pruned while history was retained, or the doc was deleted but history lingered.","commonSituations":"Inconsistent state between the `doc` (snapshot) and `history` tables after a partial cleanup/migration; a doc soft-deleted before history expiry; manual DB edits that removed the snapshot.","solutions":["Verify the current doc snapshot row exists in the workspace's `doc` table before offering rollback.","If the snapshot is genuinely gone but history exists, restore a snapshot from history into the `doc` table first (data repair), then retry rollback.","Treat `doc_not_found` from rollback as a data-integrity incident — inspect both tables for the `spaceId`/`docId`.","Prevent snapshot deletion from outpacing history cleanup in retention jobs."],"exampleFix":"// before\nawait adapter.rollbackDoc(spaceId, docId, timestamp, editorId);\n\n// after\nconst current = await adapter.getDocSnapshot(spaceId, docId);\nif (!current) {\n  throw new Error(\n    `Cannot roll back ${docId}: live snapshot missing (history exists). Data repair required.`\n  );\n}\nawait adapter.rollbackDoc(spaceId, docId, timestamp, editorId);","handlingStrategy":"validation","validationCode":"// Verify the current snapshot exists before attempting rollback\nconst current = await adapter.getDocSnapshot(spaceId, docId);\nif (!current) {\n  throw new Error(\n    `Cannot roll back ${docId}: live snapshot missing. Data repair required.`,\n  );\n}\n\nawait adapter.rollbackDoc(spaceId, docId, timestamp, editorId);","typeGuard":"function isDocSnapshot(value: unknown): value is { docId: string; bin: Uint8Array; timestamp: number } {\n  return typeof value === 'object' && value !== null &&\n    typeof (value as { docId?: unknown }).docId === 'string' &&\n    (value as { bin?: unknown }).bin instanceof Uint8Array;\n}","tryCatchPattern":"try {\n  await adapter.rollbackDoc(spaceId, docId, timestamp, editorId);\n} catch (e) {\n  if ((e as { code?: string }).code === 'doc_not_found') {\n    // data-integrity incident: snapshot missing while history exists\n    alertOps('snapshot_missing_on_rollback', { spaceId, docId });\n    throw e;\n  }\n  throw e;\n}","preventionTips":["Confirm the current snapshot row exists before offering rollback.","Treat `doc_not_found` from rollback as a data-integrity incident and investigate both tables.","Keep snapshot deletion and history cleanup consistent in retention jobs.","Restore a snapshot from history into the `doc` table before retrying rollback if the snapshot is gone."],"tags":["doc-storage","workspace","history","rollback","not-found","data-integrity"],"backgroundTag":null,"analyzedSha":"26c515e050211269e911f7d9cfe162a26c83ed98","analyzedAt":"2026-08-12T13:15:16.447Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}