max / synckit
| 1 | //! Local key/value app settings, with a per-key sync posture. |
| 2 | //! |
| 3 | //! The store half of SyncKit's config sync, and the whole of it for an app that |
| 4 | //! does not sync. A [`ConfigStore`] is a SQLite key/value table an app reads and |
| 5 | //! writes with no network in sight; a [`ConfigSpec`] declares which of those keys |
| 6 | //! may cross the sync boundary. `synckit-client` turns a spec into a |
| 7 | //! `SyncTable`, but nothing here depends on it, so a no-sync consumer — the Alloy |
| 8 | //! console remembering a theme — links this crate and not the sync runtime. |
| 9 | //! |
| 10 | //! ## Why this is its own crate, and why it is SQLite |
| 11 | //! |
| 12 | //! Four apps stored settings four ways (a synced SQLite table, an |
| 13 | //! unconditionally-synced one, `localStorage`, a TOML file) and the theme |
| 14 | //! extraction was about to make it five. The unifying substrate is SQLite rows, |
| 15 | //! not a text file, because config syncs **per key**: two devices editing |
| 16 | //! different keys must both survive (row-level HLC merge, which a whole-file blob |
| 17 | //! cannot do), and some keys must never sync at all (see posture). Rows are what |
| 18 | //! buy both. A text file would need a projection layer and a sidecar for the |
| 19 | //! per-key merge metadata — SQLite by the back door. Design note: |
| 20 | //! `synckit-config-design` in the wiki. |
| 21 | //! |
| 22 | //! ## Posture, and why it fails closed |
| 23 | //! |
| 24 | //! [`Posture`] is the one security-load-bearing type here. It descends from |
| 25 | //! audiofiles' `DeviceLocal`/`Replicated`, which exists because a hostile server |
| 26 | //! steered a local filesystem path across the sync boundary through an |
| 27 | //! unclassified config key (fuzz-2026-07-06 #2, -07-20 #1, -07-21 #3). Two rules |
| 28 | //! carried over intact: |
| 29 | //! |
| 30 | //! - The default is [`Posture::Local`]. A key crosses only when a spec declares |
| 31 | //! it [`Posture::Synced`]. |
| 32 | //! - An **undeclared** key is `Local`, never `Synced`. So the sync filter a spec |
| 33 | //! drives fails closed: a key nobody classified stays on the device rather than |
| 34 | //! leaking because someone forgot to name it. |
| 35 | |
| 36 | |
| 37 | |
| 38 | |
| 39 | |
| 40 | |
| 41 | pub use ; |
| 42 | pub use ; |
| 43 | |
| 44 | /// Re-exported so a consumer can open the [`Connection`](rusqlite::Connection) |
| 45 | /// that [`ConfigStore::open`] takes without depending on rusqlite itself, nor |
| 46 | /// version-matching it. A no-sync app (the Alloy console) links only this crate. |
| 47 | pub use rusqlite; |
| 48 |