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