{"record":{"id":"8618cddeef4d1c28","repo":"siyuan-note/siyuan","slug":"encrypted-database-history-has-no-notebook-context","errorCode":null,"errorMessage":"encrypted database history has no notebook context","messagePattern":"encrypted database history has no notebook context","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/model/attribute_view_block_history.go","lineNumber":36,"sourceCode":"func boundAttributeViewHistoryPath(historyDir, boxID, avID string) string {\n\treturn filepath.Join(historyDir, boxID, \"storage\", \"av\", avID+\".json\")\n}\n\nfunc readBoundAttributeViewHistory(filename, boxID, avID string) (*av.AttributeView, error) {\n\tdata, err := filelock.ReadFile(filename)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif boxID != \"\" {\n\t\tif !util.IsCiphertext(data) {\n\t\t\treturn nil, errors.New(\"encrypted database history is plaintext\")\n\t\t}\n\t\tdata, err = av.DecryptAVData(boxID, avID, data)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t} else if util.IsCiphertext(data) {\n\t\treturn nil, errors.New(\"encrypted database history has no notebook context\")\n\t}\n\tview, err := av.ParseAttributeViewData(avID, data)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif view.ID != avID {\n\t\treturn nil, errors.New(\"database history ID does not match its filename\")\n\t}\n\tfor _, kv := range view.KeyValues {\n\t\tif kv == nil || kv.Key == nil {\n\t\t\treturn nil, errors.New(\"database history contains an invalid field\")\n\t\t}\n\t\tfor _, value := range kv.Values {\n\t\t\tif value == nil {\n\t\t\t\treturn nil, errors.New(\"database history contains an invalid value\")\n\t\t\t}\n\t\t}\n\t}","sourceCodeStart":18,"sourceCodeEnd":54,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/9f775e8a12daef8255556097396f9b2739078892/kernel/model/attribute_view_block_history.go#L18-L54","documentation":"When reading attribute view history, ciphertext data was found but the history record carries no notebook (boxID) context. Encrypted database history cannot be decrypted without the notebook-scoped key derivation, so the engine refuses to process ciphertext whose origin notebook is unknown.","triggerScenarios":"restoreAttributeView/backup paths calling readBoundAttributeViewHistory where boxID == '' (history file not associated with a notebook, e.g. collected at top-level history scope) yet util.IsCiphertext(data) is true — typically history files moved between per-notebook and global history directories.","commonSituations":"Manually relocating history files; third-party backup tools flattening the history directory; version changes that changed where database history is stored relative to the notebook.","solutions":["Place the history file under the correct notebook's history directory and pass the matching boxID","Regenerate the history snapshot from within the encrypted notebook so box context is recorded","If the origin notebook is gone, restore it (with its keys) before restoring the database history","Do not attempt to force-decrypt; recover keys/box metadata from backup instead"],"exampleFix":null,"handlingStrategy":"validation","validationCode":"if (isCiphertext(data) && !boxID) {\n  throw new Error('ciphertext history requires notebook context (boxID)');\n}","typeGuard":"function hasBoxContext(data, boxID) {\n  return !isCiphertext(data) || !!boxID;\n}","tryCatchPattern":null,"preventionTips":["Store database history under the owning notebook's history path","Pass the originating boxID when restoring history programmatically","Do not flatten or relocate history directory structures with external tools","Restore the origin notebook (with keys) before restoring its database history"],"tags":["database","history","encryption","missing-context"],"backgroundTag":"missing-notebook-context","analyzedSha":"9f775e8a12daef8255556097396f9b2739078892","analyzedAt":"2026-09-19T03:17:15.984Z","contentChangedAt":"2026-09-19T03:17:15.984Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}