{"record":{"id":"00465dfc7c20c322","repo":"siyuan-note/siyuan","slug":"cannot-save-incomplete-notebook-crypto-configurati","errorCode":null,"errorMessage":"cannot save incomplete notebook crypto configuration","messagePattern":"cannot save incomplete notebook crypto configuration","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/model/crypto.go","lineNumber":390,"sourceCode":"\treturn nil\n}\n\n// saveNotebookCryptoBackup 把当前 NotebookCrypto（含 MasterSalt/KEKVerifier/KDFParams）备份到 DataDir。\n// kek 必须非 nil：在 Checksum 定型后计算 KEKMAC 并落盘，保证恢复路径可通过 MAC 校验。\n// 无 KEK 生成的备份 KEKMAC 必为空，会被 deriveKEK/恢复路径拒绝，等于制造无法解锁的状态（详见设计 §19）。\nfunc saveNotebookCryptoBackup(kek []byte) error {\n\tif kek == nil {\n\t\t// 无 KEK 时不得生成当前格式备份：KEKMAC 缺失会被 deriveKEK/恢复路径拒绝，\n\t\t// 生成即等于制造无法解锁的状态。\n\t\treturn errors.New(\"cannot generate notebook crypto backup without KEK\")\n\t}\n\tConf.m.Lock()\n\tnc := *Conf.NotebookCrypto // 值拷贝\n\tprepareBackupForWrite(&nc)\n\tnc.KEKMAC = computeKEKMAC(&nc, kek)\n\tif !notebookCryptoConfigurationComplete(&nc) {\n\t\tConf.m.Unlock()\n\t\treturn errors.New(\"cannot save incomplete notebook crypto configuration\")\n\t}\n\tConf.NotebookCrypto.Spec = nc.Spec\n\tConf.NotebookCrypto.BackupID = nc.BackupID\n\tConf.NotebookCrypto.CreatedAt = nc.CreatedAt\n\tConf.NotebookCrypto.Checksum = nc.Checksum\n\tConf.NotebookCrypto.KEKMAC = nc.KEKMAC // 保持 Conf 与备份文件的 KEKMAC 一致\n\tConf.m.Unlock()\n\tbackupPath := dataCryptoBackupPath()\n\tif err := os.MkdirAll(filepath.Dir(backupPath), 0755); err != nil {\n\t\treturn fmt.Errorf(\"mkdir notebook crypto backup dir failed: %w\", err)\n\t}\n\tdata, err := json.Marshal(nc)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"marshal notebook crypto backup failed: %w\", err)\n\t}\n\tif err := atomicWriteFile(backupPath, data); err != nil {\n\t\treturn fmt.Errorf(\"write notebook crypto backup failed: %w\", err)\n\t}","sourceCodeStart":372,"sourceCodeEnd":408,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/8641553a1f07374001902d3ce773285db1292b2d/kernel/model/crypto.go#L372-L408","documentation":"saveNotebookCryptoBackup refuses to persist a notebook crypto configuration that fails notebookCryptoConfigurationComplete. Before writing the backup file, it copies Conf.NotebookCrypto, runs prepareBackupForWrite, and recomputes KEKMAC from the supplied KEK; if the resulting configuration is still incomplete (missing Spec, BackupID, CreatedAt, Checksum, or KEKMAC), it aborts so an unusable backup is never written to disk and Conf is never left pointing at a partial state. This is an internal invariant guard required because restoring from an incomplete backup would break authenticated access to encrypted notebooks.","triggerScenarios":"Calling EnableEncryptedNotebook, deriveKEK, or ChangeMasterPassword when Conf.NotebookCrypto is missing required fields (empty Spec or BackupID, zero CreatedAt, or empty Checksum) so that even after prepareBackupForWrite and KEKMAC recomputation the configuration is still incomplete.","commonSituations":"Upgrading from a workspace whose conf.json predates the notebook-crypto feature and lacks the NotebookCrypto section; a corrupted or hand-edited conf.json with dropped fields; a bug or interrupted operation that wiped fields from Conf.NotebookCrypto before a KEK re-derivation was attempted.","solutions":["Recreate the configuration via EnableEncryptedNotebook so Spec, BackupID, CreatedAt, Checksum, and KEKMAC are all populated before saving","Inspect Conf.NotebookCrypto (conf.json) to find which required field is empty and restore it from the existing backup file at the data crypto backup path","If recovery material exists, use ImportNotebookCryptoBackup to load a complete backup instead of re-saving the incomplete in-memory config"],"exampleFix":"// before: re-saving a half-migrated Conf.NotebookCrypto\nerr := saveNotebookCryptoBackup(kek)\n// after: only re-save when the config is complete\nif notebookCryptoConfigurationComplete(&Conf.NotebookCrypto) {\n    err = saveNotebookCryptoBackup(kek)\n} else {\n    err = restoreFromBackupOrReenable(kek)\n}","handlingStrategy":"validation","validationCode":"if !notebookCryptoConfigurationComplete(&Conf.NotebookCrypto) {\n    // repair via backup import or re-run EnableEncryptedNotebook before saving\n}\n","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Do not hand-edit the NotebookCrypto section of conf.json","Always enable encryption through EnableEncryptedNotebook rather than editing config directly","After workspace upgrades, verify Spec, BackupID, CreatedAt, Checksum, and KEKMAC are present before KEK operations","Keep the on-disk backup file in sync; restore from it when in-memory config looks incomplete"],"tags":["crypto","configuration","notebook-encryption","backup","invariant"],"backgroundTag":"missing-required-config-field","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"}