{"record":{"id":"fa13efa7f8cadde5","repo":"siyuan-note/siyuan","slug":"document-version-is-required","errorCode":null,"errorMessage":"document version is required","messagePattern":"document version is required","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/model/history_diff.go","lineNumber":117,"sourceCode":"\tsignature string\n}\n\ntype docTextSegment struct {\n\tnode       *ast.Node\n\tstart      int\n\tend        int\n\tstoredRuns []string\n\tsignature  string\n}\n\ntype docDiffLCSBudget struct {\n\tremaining int\n}\n\n// ResolveDocVersionBoxID 返回文档版本引用中明确记录的加密笔记本 ID。\nfunc ResolveDocVersionBoxID(ref *DocVersionRef) (string, error) {\n\tif ref == nil {\n\t\treturn \"\", errors.New(\"document version is required\")\n\t}\n\tswitch ref.Type {\n\tcase docVersionCurrent:\n\t\tif !ast.IsNodeIDPattern(ref.ID) {\n\t\t\treturn \"\", errors.New(\"current document ID is invalid\")\n\t\t}\n\t\tblockTree := treenode.GetBlockTree(ref.ID)\n\t\tif blockTree == nil {\n\t\t\treturn \"\", ErrTreeNotFound\n\t\t}\n\t\tif IsEncryptedBox(blockTree.BoxID) {\n\t\t\treturn blockTree.BoxID, nil\n\t\t}\n\t\treturn \"\", nil\n\tcase docVersionHistory:\n\t\tabsPath, err := validateHistoryPath(ref.Path)\n\t\tif err != nil {\n\t\t\treturn \"\", err","sourceCodeStart":99,"sourceCodeEnd":135,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/251596fc0de2f9528c00c224252fd073a99973f4/kernel/model/history_diff.go#L99-L135","documentation":"Returned by ResolveDocVersionBoxID (history_diff.go:117) when the supplied *DocVersionRef is nil. ResolveDocVersionBoxID walks the ref to find which encrypted notebook a version belongs to, so a nil ref has no type/ID/path to inspect. This is the first guard before the type switch and exists to fail fast on missing input.","triggerScenarios":"POST /api/history/diffDocVersions or /api/history/rollbackDocHistory invoked with a missing or null \"left\"/\"right\" object that deserializes to a nil pointer; a Go caller passing nil instead of an empty DocVersionRef{}.","commonSituations":"Frontend sends {\"left\": null, \"right\": {...}}; a plugin/MCP tool omits the version object; refactoring that introduces an optional pointer without a default.","solutions":["Ensure the caller always constructs a non-nil DocVersionRef before invoking ResolveDocVersionBoxID or DiffDocVersions.","At the API boundary, reject nil refs with util.ParseJsonArgs/BindJsonArg so the user gets a clear \"X document version is required\" message before reaching model code (see api/history.go:200-203 for the right-side example).","Default to an empty struct (&DocVersionRef{}) only if downstream code tolerates an empty Type; otherwise surface the missing-field error to the user."],"exampleFix":"// before\nboxID, err := ResolveDocVersionBoxID(ref) // ref may be nil\n\n// after\nif ref == nil {\n    return \"\", errors.New(\"document version is required\")\n}\nboxID, err := ResolveDocVersionBoxID(ref)","handlingStrategy":"validation","validationCode":"if ref == nil {\n    return \"\", errors.New(\"document version is required\")\n}\nreturn ResolveDocVersionBoxID(ref)","typeGuard":"// validDocVersionRef is a non-nil ref with a known type.\nfunc validDocVersionRef(r *DocVersionRef) bool {\n    if r == nil { return false }\n    switch r.Type {\n    case docVersionCurrent, docVersionHistory, docVersionSnapshot:\n        return true\n    }\n    return false\n}","tryCatchPattern":null,"preventionTips":["At the API boundary, require the \"left\"/\"right\" JSON objects so nil never reaches model code.","Default-construct refs (&DocVersionRef{}) only when downstream tolerates an empty Type.","Unit-test ResolveDocVersionBoxID with nil to confirm it fails fast."],"tags":["nil-guard","history-diff","validation","api"],"backgroundTag":null,"analyzedSha":"251596fc0de2f9528c00c224252fd073a99973f4","analyzedAt":"2026-08-12T21:18:37.123Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}