{"record":{"id":"00eebf53d20ab848","repo":"siyuan-note/siyuan","slug":"conf-language-312","errorCode":null,"errorMessage":"Conf.Language(312)","messagePattern":"Conf\\.Language\\(312\\)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/model/crypto.go","lineNumber":1008,"sourceCode":"\treturn EnableEncryptedNotebook(password)\n}\n\n// EnableEncryptedNotebook 启用加密笔记本功能：生成 MasterSalt、派生 KEK、写入校验值并持久化。\n// 重复调用（已启用）返回错误，避免覆盖现有加密笔记本的密钥参数。\n// KEK 不缓存——启用后用户需对每个加密笔记本单独调 UnlockBox 解锁。\nfunc EnableEncryptedNotebook(password string) error {\n\tif len(password) == 0 {\n\t\treturn errors.New(\"password must not be empty\")\n\t}\n\n\tnotebookCryptoMu.Lock()\n\tdefer notebookCryptoMu.Unlock()\n\n\tConf.m.RLock()\n\tcurrent := *Conf.NotebookCrypto\n\tConf.m.RUnlock()\n\tif current.Enabled && notebookCryptoConfigurationComplete(&current) {\n\t\treturn errors.New(Conf.Language(312))\n\t}\n\n\thasEncrypted, listErr := hasEncryptedNotebook()\n\tif listErr != nil {\n\t\treturn fmt.Errorf(\"list encrypted notebooks failed: %w\", listErr)\n\t}\n\thasHistory, historyErr := scanEncryptedNotebookHistory()\n\tif historyErr != nil {\n\t\treturn fmt.Errorf(\"check encrypted notebook history failed: %w\", historyErr)\n\t}\n\thasBackup := filelock.IsExist(dataCryptoBackupPath())\n\tif hasEncrypted || hasHistory || hasBackup {\n\t\t// 现存笔记本、已删除笔记本历史或全局备份均表示已有密钥域，必须恢复并认证，不能生成新 MasterSalt。\n\t\tkek, restoreErr := tryRestoreNotebookCryptoFromBackupLocked(password)\n\t\tif kek != nil {\n\t\t\tzeroAndClear(kek)\n\t\t}\n\t\tif restoreErr != nil {","sourceCodeStart":990,"sourceCodeEnd":1026,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/8641553a1f07374001902d3ce773285db1292b2d/kernel/model/crypto.go#L990-L1026","documentation":"EnableEncryptedNotebook rejects the call when the feature is already enabled and fully configured (Conf.NotebookCrypto.Enabled plus notebookCryptoConfigurationComplete). The localized message Conf.Language(312) is 'Encrypted notebook feature is already enabled'. Re-enabling would overwrite the existing MasterSalt/KEK parameters, so the guard protects existing encrypted notebooks.","triggerScenarios":"Calling EnableEncryptedNotebook a second time after a successful first enable; the frontend settings toggle invoking enable while the feature flag is already on and complete in conf.json.","commonSituations":"Double-invocation from a UI retry loop or duplicate request; a script that enables the feature idempotently; state restored from an existing workspace conf where the feature was already turned on.","solutions":["Check Conf.NotebookCrypto.Enabled (or the API/config state) before calling EnableEncryptedNotebook and skip if already enabled","Treat this message as success/idempotent no-op in automation instead of retrying","If the intent is to change keys, disable the feature first (after removing encrypted notebooks) then re-enable"],"exampleFix":"// before\nEnableEncryptedNotebook(password)\n// after\nif !Conf.NotebookCrypto.Enabled { EnableEncryptedNotebook(password) }","handlingStrategy":"validation","validationCode":"if Conf.NotebookCrypto.Enabled && Conf.NotebookCrypto.MasterSalt != \"\" {\n\t// already enabled; skip\n\treturn nil\n}\nreturn EnableEncryptedNotebook(password)","typeGuard":null,"tryCatchPattern":"if err := EnableEncryptedNotebook(pw); err != nil && err.Error() == Conf.Language(312) {\n\t// idempotent no-op\n\treturn nil\n}","preventionTips":["Make enable operations idempotent by checking current state first","Do not wire the enable call into retry loops without state checks"],"tags":["go","encryption","idempotency"],"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"}