保管箱設定檔是區段鍵值存放,不是一列巨無霸
排程中閱讀時間 7 分鐘 作者 NT²
鹽值、保管箱金鑰 DID 公開材料、同步游標與引導偏好,不是同一列肥大的 SQL。它們以具名的 保管箱設定檔區段存在——讓備份、同步與解鎖各自只碰需要的部分。
保管箱設定檔是區段鍵值存放,不是一列巨無霸
主張:保管箱設定檔是區段存放區
每個 NT² Vault 都不只是加密資產。它還有一份保管箱設定檔:你在這台裝置上取的顯示名稱、永不離開本機的 KDF 鹽值、用於雲端身分的保管箱金鑰 DID 公開材料、解鎖用的包裹金鑰材料、NT² Premium 同步游標、WebAuthn 綁定,以及一組持續成長的偏好——健康報告開關、首次引導狀態、旅行模式設定,以及明年產品還會再加的東西。
那份設定檔與資產目錄住在同一個每保管箱 SQLite 資料庫裡——瀏覽器走 OPFS,桌面殼層則用原生檔案。資料表仍叫 vault_meta。重要的主張關乎形狀,不是名稱:
保管箱設定檔是區段鍵值存放區:每個邏輯區段一列,不是擁有數十欄的一列巨無霸。
每個區段有穩定的鍵(identity、did、sync、prefs.health 等)與版本化的 JSON 承載。解鎖讀身分區段。備份匯出可攜區段。replica 同步運送可同步區段。裝置綁定材料從不假裝它屬於你帶到另一台筆電的檔案。
若只記得一句話:我們依關注點分組保管箱層級狀態,讓遷移、備份與同步不再為同一列肥資料打架。
約束:巨無霸列會吃掉產品
這種失敗模式很眼熟。先從單一 vault_meta 列開始——id = 'meta',再加上鹽值、顯示名稱、幾個旗標欄。每個新功能加一欄。每一欄需要一次 ALTER TABLE。每條解鎖路徑學會 SELECT *。每個備份匯出器得逐欄決定什麼能安全複製。每位同步工程師繼承一團混雜的關注點:引導用密碼學材料旁邊是「這台筆電的上次同步游標」,旁邊又是「使用者是否關掉首次引導提示」。
那種形狀會一次失敗三種方式。
結構描述 churn 變成功能稅。 偏好與裝置綁定是產品表面,不是關聯式結構描述。為了「顯示保管箱健康報告」去強迫一次 SQLite 欄位遷移,等於把偏好開關變成結構描述事件。上線後的漸進式遷移已經是嚴肅承諾;把版本戳記燒在偏好欄位上,用錯了貨幣。
可攜與裝置本機糾纏在一起。 鹽值與保管箱金鑰 DID 公開材料必須能隨可攜備份或已註冊 replica 旅行。同步游標不行。WebAuthn 憑證綁定在平台驗證器上。若一切塞在同一列,「匯出設定檔」就變成手寫允許清單——容易出錯、難審查,還會誘惑人「以防萬一」多匯出一點。
解鎖為閣樓買單。 冷開啟應讀取鹽值與驗證資料、派生金鑰,並判定鎖定或解鎖。它不該為了回答那個問題而載入旅行偏好、引導旗標與同步帳簿。巨無霸列邀請全有或全無的讀取。區段存放區讓解鎖保持狹窄。
local-first 產品很早就會感受到這種壓力。保管箱檔案是權威來源。零知識意味著雲端無法重建缺少的本機設定檔。因此設定檔版面不是實作細節——它是安全與耐久故事的一部分。單一寬列會把故事糊過去,直到備份、同步與解鎖對「設定檔」到底指什麼意見不合。
設計:具名區段、版本化承載、三種旅行階級
資料表刻意很小:
| 欄位 | 角色 |
|---|---|
| 區段鍵 | 單一關注點的穩定 id(identity、did、sync、prefs.* 等) |
| 承載 | 版本化 JSON 物件(v 加上欄位) |
| 更新時間 | 該區段上次寫入的 Unix 毫秒 |
一個保管箱資料庫持有許多區段列。沒有「只能有一列 meta」的檢查。新的產品表面通常意味著新的區段鍵與 TypeScript 剖析器——不是新的 SQL 欄。
承載在靜止時是明文 JSON,信任模型與資產標題相同:只有你本機已有保管箱檔案時才讀得到。它們不存放已解密的資產欄位或主密碼。確實住在區段裡的敏感密碼學材料(包裹金鑰、驗證密文)留在為此設計的區段;版面不會變成明文祕密的傾倒場。
領域程式碼仍可為想看全貌的呼叫端呈現扁平的「保管箱 meta」視圖。底層寫入會路由到正確區段。那層 facade 是便利;存放真相是區段式的。
旅行行為是設計的另一半。區段落入不同階級:
| 階級 | 例子 | 備份/檔案匯出 | Replica 同步 |
|---|---|---|---|
| 可攜/可同步 | 身分(鹽值、顯示名稱)、保管箱金鑰 DID 公開材料、包裹的保管箱金鑰材料、多數偏好 | 納入 | 註冊後納入 |
| 裝置本機 | 同步游標與旗標、WebAuthn 綁定、這台機器的備份 UI 偏好 | 省略 | 省略 |
| 註冊引導 | 冷裝置上的首次鹽值/多控因子 | 不能代替註冊 | 先經還原包/設定 QR 註冊 |
可攜區段讓 .nt2backup 感覺像主權:你帶走的是另一台裝置在正確還原路徑後需要的設定檔片段——不是另一台筆電的同步水位。裝置本機區段對物理保持誠實:游標是「這個 replica 拉到哪」,不是抽象邏輯保管箱的屬性。只在某個驗證器上有意義的 WebAuthn 材料,絕不能當成可攜身分,經 USB 隨身碟來回旅行。
解鎖刻意保持無聊。它讀身分區段——鹽值與本機密碼驗證資料——然後才進入多控組合、保管箱金鑰 DID 解包與工作階段狀態。偏好與同步帳簿等到保管箱真正開啟再說。錯誤主密碼仍會失敗關閉;區段版面不改變那條規則,只是讓閘門保持小。
對 replica 同步而言,可同步區段與加密資產列搭乘同一套盲目批次格式:邊緣存放密文與非敏感中繼資料,不是一本可讀的人生傳記。最後寫入獲勝的合併以區段鍵為單位。若裝置本機鍵莫名出現在線路上,就丟掉,而不是「好心」套用。備份匯入本身不會發明一個新的已註冊 replica——註冊與可攜設定檔是兩扇刻意分開的門。
flowchart LR
subgraph vaultDb [每保管箱 SQLite]
idSec[identity]
didSec[did]
syncSec[sync]
prefsSec[prefs.*]
deviceSec[device.*]
end
unlock[解鎖路徑] --> idSec
backup[.nt2backup 可攜] --> idSec
backup --> didSec
backup --> prefsSec
replica[Replica 批次] --> idSec
replica --> didSec
replica --> prefsSec
syncSec -.->|留在本機| localOnly[僅此機器]
deviceSec -.->|留在本機| localOnly
圖是產品規則的圖像版:許多區段、一個資料庫、不同的旅行者。
取捨:用剖析器與紀律,換 SQL 便利
區段鍵值存放不是免費的。
我們在應用程式碼擁有剖析器。 每個區段有版本化承載與具名剖析路徑。未知鍵為向前相容而忽略;破壞性變更上線時,錯誤的 v 會失敗關閉。我們不把 SQLite json_extract 當成偏好的查詢語言。這讓 SQL 保持無聊,把結構描述演進放在 TypeScript 已在的地方——但也代表每個新區段需要登錄、預設值與測試,而不只是 UI 開關。
我們同時維護 facade 與真相。 想要扁平設定檔的呼叫點拿到彙總視圖。不該碰同步狀態的呼叫點使用區段 API。兩種讀取同一存放區的方式,若寫入者繞過路由器就會漂移。紀律是:部分更新走正確區段;不要在領域程式碼裡重新發明寬列 UPDATE。
我們接受更多列,換更清楚的邊界。 一個偏好命名空間本來可以是三十欄。點分隔鍵(prefs.health、prefs.onboarding)把相關欄位放在一起,又不退回巨無霸列的引力。我們也拒絕另一極端——每個純量一鍵會讓列數與交易爆炸,卻換不到清晰。
我們把備份與同步允許清單耦合到登錄表。 可攜與可同步集合必須與註冊、還原真正需要的東西對齊。沒有旅行階級的新區段,是等著變成支援工單的 bug。好處是可審查:安全讀者可以問「同步會匯出這個鍵嗎?」而不是稽核四十欄。
我們換到的,是巨無霸列永遠無法乾淨交付的性質:可加的產品偏好不必伴隨可加的 SQL 遷移、狹窄的解鎖,以及誠實切分什麼可以離開裝置、什麼不能。
我們拒絕什麼
我們拒絕把單一巨無霸列當成長期保管箱設定檔。 欄位膨脹不是存放策略。
我們拒絕把 ALTER TABLE 當新偏好的預設路徑。 偏好是區段承載。結構描述版本戳記留給解鎖、列表與 FTS 真正依賴的關聯形狀。
我們拒絕把裝置本機狀態匯出成彷彿可攜身分。 同步游標、WebAuthn 綁定與僅限本機的偏好,不屬於聲稱能搬移保管箱的備份檔。
我們拒絕把備份匯入當成註冊。 可攜區段在保管箱已存在後還原設定檔;它們不鑄造多控因子,也不假裝一台冷筆電已經是 replica。
我們拒絕載入整座設定檔閣樓的解鎖。 先身分。其餘等閘門打開再說。
我們拒絕把已解密的資產承載塞進設定檔區段。 區段存放區是保管箱層級中繼資料與密碼學帳簿——不是第二張資產表。
我們拒絕把雲端當鹽值或驗證資料的神諭。 那些住在裝置上的本機身分區段。邊緣永遠不該變成你去抓「夠用來猜主密碼」的地方。
結語:設定檔形狀是 local-first 功能
區段鍵值存放聽起來像內部重構。它其實是產品成長時,local-first 如何保持誠實的方式。
具名區段讓鹽值留在裝置、保管箱金鑰 DID 材料可稽核、該可攜的偏好可攜、該本機的同步帳簿本機。同一份每保管箱 SQLite 檔案承載你的加密目錄,也承載一份能演進而無須清除、能旅行又不拖走另一台機器狀態的設定檔。
關於那份 SQLite 檔案如何在上線後升級而無須重建,請讀結構描述 v60 與漸進式遷移。關於鹽值為何從不變成雲端抓取,請讀KDF 鹽值留在你的裝置上。關於瀏覽器裡保管箱資料庫住在哪,請讀為什麼保管箱 SQLite 資料庫放在 OPFS,而不是 IndexedDB。關於已註冊 replica 如何在不教邊緣讀你的前提下搬移密文,請讀在邊緣進行盲目 replica 同步。
若你想要一份依設計就是區段式——而不是等著變成遷移稅的巨無霸列——的保管箱設定檔,試試 NT² Vault,或到 nt2.me 了解更多。
最後更新 2026-10-03
相關故事
- 離線可用不是「有網路再碰運氣同步」
閱讀時間 6 分鐘
- Replica 批次內部——封包形狀
閱讀時間 7 分鐘
- 來自一萬筆項目 OPFS 保管箱的基準測試
閱讀時間 7 分鐘