{"record":{"id":"46936d57d65d3ccf","repo":"siyuan-note/siyuan","slug":"encrypted-notebook-attribute-view-history-is-plain","errorCode":null,"errorMessage":"encrypted notebook attribute view history is plaintext [%s]","messagePattern":"encrypted notebook attribute view history is plaintext \\[(.+?)\\]","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/model/history.go","lineNumber":697,"sourceCode":"\t}\n\n\tfrom := historyPath\n\t// 从路径提取 boxID 判断是否加密笔记本的 AV\n\trelPath := strings.TrimPrefix(filepath.ToSlash(historyPath), filepath.ToSlash(util.HistoryDir))\n\trelPath = strings.TrimPrefix(relPath, \"/\")\n\tpathParts := strings.SplitN(relPath, \"/\", 3)\n\tdata, readErr := filelock.ReadFile(from)\n\tif readErr != nil {\n\t\treturn readErr\n\t}\n\tciphertext := util.IsCiphertext(data)\n\tif ciphertext && (len(pathParts) < 3 || !ast.IsNodeIDPattern(pathParts[1]) || !IsEncryptedBox(pathParts[1])) {\n\t\treturn errors.New(\"encrypted attribute view history is missing valid notebook context\")\n\t}\n\tto := filepath.Join(util.DataDir, \"storage\", \"av\", filepath.Base(historyPath))\n\tif len(pathParts) >= 2 && IsEncryptedBox(pathParts[1]) {\n\t\tif !ciphertext {\n\t\t\treturn fmt.Errorf(\"encrypted notebook attribute view history is plaintext [%s]\", pathParts[1])\n\t\t}\n\t\t// 加密笔记本的 AV 定义回滚到笔记本级目录\n\t\tto = filepath.Join(util.DataDir, pathParts[1], \"storage\", \"av\", filepath.Base(historyPath))\n\t\tif err = os.MkdirAll(filepath.Dir(to), 0755); err != nil {\n\t\t\treturn\n\t\t}\n\t}\n\n\tif err = filelock.CopyNewtimes(from, to); err != nil {\n\t\tlogging.LogErrorf(\"copy file [%s] to [%s] failed: %s\", from, to, err)\n\t\treturn\n\t}\n\tavID := strings.TrimSuffix(filepath.Base(historyPath), \".json\")\n\tcache.RemoveAVData(avID)\n\tavBoxID := \"\"\n\tif len(pathParts) >= 2 && IsEncryptedBox(pathParts[1]) {\n\t\tavBoxID = pathParts[1]\n\t}","sourceCodeStart":679,"sourceCodeEnd":715,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/8641553a1f07374001902d3ce773285db1292b2d/kernel/model/history.go#L679-L715","documentation":"When rolling back an AV history for an encrypted notebook (path contains an encrypted boxID), the code requires the history snapshot file itself to be ciphertext. If the stored history JSON is plaintext while the notebook is encrypted, the snapshot violates the encrypted-notebook storage contract and restoring it would write unencrypted attribute-view definitions into an encrypted notebook. The error names the offending boxID so the bad snapshot can be located.","triggerScenarios":"Calling RollbackAttributeViewHistory with a history path under an encrypted notebook ID (pathParts[1] passes IsEncryptedBox) but the history file read from disk fails util.IsCiphertext — e.g. the .json was saved in plaintext, produced by old tooling, or partially re-written by a sync/restore process.","commonSituations":"Restoring history snapshots from a backup made before encrypted-notebook AV support; third-party scripts that copy storage/av JSONs around without encrypting them; a mixed-version workspace where an older kernel wrote plaintext AV history for an encrypted notebook.","solutions":["Replace the plaintext history snapshot at that path with a properly encrypted one from a valid backup","Do not roll back this snapshot; manually recreate the attribute view inside the encrypted notebook so it is written encrypted","Verify with util.IsCiphertext (or open the file and confirm it is ciphertext) before attempting rollback again","Check kernel version: if an older build produced the plaintext history, regenerate history after upgrading"],"exampleFix":null,"handlingStrategy":"validation","validationCode":"const data = await readFile(historyPath);\nconst isCiphertext = (buf) => !Buffer.from(buf.slice(0, 64)).toString(\"utf8\").trimStart().startsWith(\"{\");\nconst notebookIsEncrypted = isEncryptedBox(boxIDFromPath);\nif (notebookIsEncrypted && !isCiphertext(data)) {\n  throw new Error(\"plaintext AV history for encrypted notebook \" + boxIDFromPath);\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Only generate AV history snapshots with the same kernel version that supports encrypted notebooks","Verify snapshot encryption before archiving history directories from encrypted notebooks","Do not hand-edit or re-serialize AV history JSON files"],"tags":["history","attribute-view","encryption","plaintext"],"backgroundTag":"invalid-state-transition","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"}