Staging — 僅供部落格預覽。
跳至主要內容

保管箱設定檔是區段鍵值存放,不是一列巨無霸

排程中

閱讀時間 7 分鐘 作者 NT²

鹽值、保管箱金鑰 DID 公開材料、同步游標與引導偏好,不是同一列肥大的 SQL。它們以具名的 保管箱設定檔區段存在——讓備份、同步與解鎖各自只碰需要的部分。

保管箱設定檔是區段鍵值存放,不是一列巨無霸

主張:保管箱設定檔是區段存放區

每個 NT² Vault 都不只是加密資產。它還有一份保管箱設定檔:你在這台裝置上取的顯示名稱、永不離開本機的 KDF 鹽值、用於雲端身分的保管箱金鑰 DID 公開材料、解鎖用的包裹金鑰材料、NT² Premium 同步游標、WebAuthn 綁定,以及一組持續成長的偏好——健康報告開關、首次引導狀態、旅行模式設定,以及明年產品還會再加的東西。

那份設定檔與資產目錄住在同一個每保管箱 SQLite 資料庫裡——瀏覽器走 OPFS,桌面殼層則用原生檔案。資料表仍叫 vault_meta。重要的主張關乎形狀,不是名稱:

保管箱設定檔是區段鍵值存放區:每個邏輯區段一列,不是擁有數十欄的一列巨無霸。

每個區段有穩定的鍵(identitydidsyncprefs.health 等)與版本化的 JSON 承載。解鎖讀身分區段。備份匯出可攜區段。replica 同步運送可同步區段。裝置綁定材料從不假裝它屬於你帶到另一台筆電的檔案。

若只記得一句話:我們依關注點分組保管箱層級狀態,讓遷移、備份與同步不再為同一列肥資料打架。

約束:巨無霸列會吃掉產品

這種失敗模式很眼熟。先從單一 vault_meta 列開始——id = 'meta',再加上鹽值、顯示名稱、幾個旗標欄。每個新功能加一欄。每一欄需要一次 ALTER TABLE。每條解鎖路徑學會 SELECT *。每個備份匯出器得逐欄決定什麼能安全複製。每位同步工程師繼承一團混雜的關注點:引導用密碼學材料旁邊是「這台筆電的上次同步游標」,旁邊又是「使用者是否關掉首次引導提示」。

那種形狀會一次失敗三種方式。

結構描述 churn 變成功能稅。 偏好與裝置綁定是產品表面,不是關聯式結構描述。為了「顯示保管箱健康報告」去強迫一次 SQLite 欄位遷移,等於把偏好開關變成結構描述事件。上線後的漸進式遷移已經是嚴肅承諾;把版本戳記燒在偏好欄位上,用錯了貨幣。

可攜與裝置本機糾纏在一起。 鹽值與保管箱金鑰 DID 公開材料必須能隨可攜備份或已註冊 replica 旅行。同步游標不行。WebAuthn 憑證綁定在平台驗證器上。若一切塞在同一列,「匯出設定檔」就變成手寫允許清單——容易出錯、難審查,還會誘惑人「以防萬一」多匯出一點。

解鎖為閣樓買單。 冷開啟應讀取鹽值與驗證資料、派生金鑰,並判定鎖定或解鎖。它不該為了回答那個問題而載入旅行偏好、引導旗標與同步帳簿。巨無霸列邀請全有或全無的讀取。區段存放區讓解鎖保持狹窄。

local-first 產品很早就會感受到這種壓力。保管箱檔案是權威來源。零知識意味著雲端無法重建缺少的本機設定檔。因此設定檔版面不是實作細節——它是安全與耐久故事的一部分。單一寬列會把故事糊過去,直到備份、同步與解鎖對「設定檔」到底指什麼意見不合。

設計:具名區段、版本化承載、三種旅行階級

資料表刻意很小:

欄位角色
區段鍵單一關注點的穩定 id(identitydidsyncprefs.* 等)
承載版本化 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.healthprefs.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

相關故事