{"record":{"id":"bfc45eb116839914","repo":"siyuan-note/siyuan","slug":"encrypted-attribute-view-history-is-missing-notebo","errorCode":null,"errorMessage":"encrypted attribute view history is missing notebook context [%s]","messagePattern":"encrypted attribute view history is missing notebook context \\[(.+?)\\]","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/model/history.go","lineNumber":384,"sourceCode":"\tif tree == nil {\n\t\treturn errors.New(\"parse document history failed\")\n\t}\n\tif encrypted && tree.Root.ID+\".sy\" != filepath.Base(historyPath) {\n\t\treturn errors.New(\"encrypted document history root ID does not match its filename\")\n\t}\n\tif nil != tree {\n\t\thistoryDir := filepath.Join(util.HistoryDir, parts[0])\n\n\t\tavNodes := tree.Root.ChildrenByType(ast.NodeAttributeView)\n\t\tfor _, avNode := range avNodes {\n\t\t\tsrcAvPath := filepath.Join(historyDir, \"storage\", \"av\", avNode.AttributeViewID+\".json\")\n\t\t\t// 加密笔记本的 AV 定义在笔记本级目录\n\t\t\tdestAvPath := filepath.Join(util.DataDir, \"storage\", \"av\", avNode.AttributeViewID+\".json\")\n\t\t\tif IsEncryptedBox(boxID) {\n\t\t\t\t// 历史目录里 AV 也可能在 boxID 子目录下\n\t\t\t\tboxSrcAvPath := filepath.Join(historyDir, boxID, \"storage\", \"av\", avNode.AttributeViewID+\".json\")\n\t\t\t\tif !gulu.File.IsExist(boxSrcAvPath) {\n\t\t\t\t\treturn fmt.Errorf(\"encrypted attribute view history is missing notebook context [%s]\", avNode.AttributeViewID)\n\t\t\t\t}\n\t\t\t\tsrcAvPath = boxSrcAvPath\n\t\t\t\tdestAvPath = filepath.Join(util.DataDir, boxID, \"storage\", \"av\", avNode.AttributeViewID+\".json\")\n\t\t\t}\n\t\t\tif gulu.File.IsExist(destAvPath) {\n\t\t\t\tif copyErr := filelock.CopyNewtimes(srcAvPath, destAvPath); nil != copyErr {\n\t\t\t\t\tlogging.LogErrorf(\"copy av [%s] failed: %s\", srcAvPath, copyErr)\n\t\t\t\t}\n\t\t\t\tcache.RemoveAVData(avNode.AttributeViewID)\n\t\t\t}\n\n\t\t\tavIDs = append(avIDs, avNode.AttributeViewID)\n\t\t}\n\t}\n\tavIDs = gulu.Str.RemoveDuplicatedElem(avIDs)\n\n\ttree.Box = boxID\n\ttree.Path = filepath.ToSlash(strings.TrimPrefix(destPath, util.DataDir+string(os.PathSeparator)+boxID))","sourceCodeStart":366,"sourceCodeEnd":402,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/8641553a1f07374001902d3ce773285db1292b2d/kernel/model/history.go#L366-L402","documentation":"When rolling back a document that contains attribute views (databases) from an encrypted notebook, the kernel needs the AV definition file from history. For encrypted boxes the AV may live under the boxID subdirectory of the history entry; if that file does not exist, the kernel cannot restore the database consistently with the document and fails with this error naming the attribute view ID.","triggerScenarios":"rollbackDocHistory on an encrypted notebook's document containing NodeAttributeView nodes, where data/history/<date>/<boxID>/storage/av/<avID>.json is absent — history was captured before the AV was created, or the history entry lacks the storage/av portion.","commonSituations":"Rolling back to a very old snapshot that predates the database block; history pruning that removed storage/av files but kept document snapshots; manually cleaning the history directory and deleting AV files.","solutions":["Ensure the history entry includes storage/av/<avID>.json under the boxID directory before rolling back","Choose a more recent history snapshot that contains the AV definition","Restore the missing AV history file from backup, then retry the rollback","Remove the database block from the document (or accept losing AV rollback) if the AV history is unrecoverable"],"exampleFix":"// before: snapshot without AV history\nls data/history/20260910/encBox/storage/av/  # missing 20250101120000-av1.json\n// after: pick a snapshot that includes it\nls data/history/20260911/encBox/storage/av/  # 20250101120000-av1.json present, use that entry","handlingStrategy":"validation","validationCode":"const avIds = docAvIds // attribute view IDs referenced by the document\nfor (const avId of avIds) {\n  const p = `data/history/${dateSeg}/${boxID}/storage/av/${avId}.json`\n  if (!(await fileExists(p))) throw new Error(`history missing AV file ${avId}`)\n}","typeGuard":null,"tryCatchPattern":"try {\n  await fetchPost(\"/api/history/rollbackDocHistory\", {historyPath})\n} catch (e) {\n  if (String(e.msg || e).includes(\"missing notebook context\")) {\n    // select a snapshot that includes storage/av definitions\n  }\n}","preventionTips":["Roll back to snapshots known to include the document's databases","Do not delete storage/av files when pruning history manually","Keep document and AV history generation times aligned by using built-in snapshot flows"],"tags":["go","history","encryption","attribute-view","file-not-found"],"backgroundTag":"file-not-found","analyzedSha":"8641553a1f07374001902d3ce773285db1292b2d","analyzedAt":"2026-09-11T16:08:28.414Z","contentChangedAt":"2026-09-11T16:08:28.414Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}