{"record":{"id":"d0ac5fc86b4556e7","repo":"toeverything/AFFiNE","slug":"doc-history-not-found","errorCode":"doc_history_not_found","errorMessage":"History of ${docId} at ${timestamp} under Space ${spaceId}.","messagePattern":"History of (.+?) at (.+?) under Space (.+?)\\.","errorType":"exception","errorClass":"DocHistoryNotFound","httpStatus":404,"severity":"error","filePath":"packages/backend/server/src/core/doc/adapters/workspace.ts","lineNumber":207,"sourceCode":"    return {\n      spaceId: workspaceId,\n      docId,\n      bin: history.blob,\n      timestamp: history.timestamp,\n      editor: history.editor?.id,\n    };\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:","sourceCodeStart":189,"sourceCodeEnd":225,"githubUrl":"https://github.com/toeverything/AFFiNE/blob/26c515e050211269e911f7d9cfe162a26c83ed98/packages/backend/server/src/core/doc/adapters/workspace.ts#L189-L225","documentation":"Thrown by `rollbackDoc` when `getDocHistory(spaceId, docId, timestamp)` returns null — i.e., no history snapshot exists for that exact `timestamp`. The rollback cannot proceed without a target history record, so it aborts with `DocHistoryNotFound` (category `resource_not_found`), interpolating `docId`, `timestamp`, and `spaceId` into the message.","triggerScenarios":"Requesting a rollback to a `timestamp` for which no history row exists: the timestamp predates history retention, was pruned by `historyMaxAge`, was never snapshotted (interval not elapsed), or the doc has no history at all.","commonSituations":"History cleanup/expiry removing older rows; a client sending a stale timestamp from an old history list; workspaces whose `historyMaxAge`/`historyMinInterval` config suppresses frequent snapshots; rollback after the history table was trimmed.","solutions":["Re-fetch the available history list (`listDocHistories`) and only offer rollback to timestamps present in it.","Verify the requested `timestamp` exactly matches a stored history row (not a client-approximated value).","Check the workspace's history retention config (`historyMaxAge`) — expired history cannot be restored.","Surface `doc_history_not_found` to the user as 'this history version is no longer available'."],"exampleFix":"// before\nawait adapter.rollbackDoc(spaceId, docId, pickedTimestamp, editorId);\n\n// after\nconst available = await adapter.listDocHistories(spaceId, docId, { limit: 50 });\nif (!available.some(h => h.timestamp === pickedTimestamp)) {\n  toast('This history version is no longer available.');\n  return;\n}\nawait adapter.rollbackDoc(spaceId, docId, pickedTimestamp, editorId);","handlingStrategy":"validation","validationCode":"// Only offer rollback to timestamps present in the stored history\nconst history = await adapter.listDocHistories(spaceId, docId, { limit: 100 });\nconst validTimestamps = new Set(history.map(h => h.timestamp));\n\nif (!validTimestamps.has(requestedTimestamp)) {\n  toast('This history version is no longer available.');\n  return;\n}\n\nawait adapter.rollbackDoc(spaceId, docId, requestedTimestamp, editorId);","typeGuard":"function isHistoryEntry(value: unknown): value is { timestamp: number; blob: Buffer } {\n  return typeof value === 'object' && value !== null &&\n    typeof (value as { timestamp?: unknown }).timestamp === 'number';\n}","tryCatchPattern":"try {\n  await adapter.rollbackDoc(spaceId, docId, requestedTimestamp, editorId);\n} catch (e) {\n  if ((e as { code?: string }).code === 'doc_history_not_found') {\n    toast('This history version is no longer available.');\n    return;\n  }\n  throw e;\n}","preventionTips":["Populate the rollback picker from `listDocHistories`, not from a stale client cache.","Ensure the requested `timestamp` exactly matches a stored history row.","Account for `historyMaxAge`/`historyMinInterval` retention when offering versions.","Refresh the history list right before rolling back."],"tags":["doc-storage","workspace","history","rollback","not-found","retention"],"backgroundTag":null,"analyzedSha":"26c515e050211269e911f7d9cfe162a26c83ed98","analyzedAt":"2026-08-12T13:15:16.447Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}