max / balanced_breakfast
| 1 | //! Single source of truth for `user_config` keys and their sync posture. |
| 2 | //! |
| 3 | //! BalancedBreakfast used to sync every `user_config` key unconditionally: the |
| 4 | //! table's triggers enqueued a changelog row for any key, and the initial |
| 5 | //! snapshot swept the whole table. That is the same shape audiofiles was |
| 6 | //! hardened away from (fuzz-2026-07-06 #2, -07-20 #1, -07-21 #3): a config key |
| 7 | //! that names device state must never cross the sync boundary, and "sync |
| 8 | //! everything" cannot hold one back. |
| 9 | //! |
| 10 | //! [`CONFIG`] is the family's shared [`ConfigSpec`] for BB's `user_config` |
| 11 | //! table, declaring each known key's [`Posture`]. It drives two filters: |
| 12 | //! |
| 13 | //! - the SQL export filter (the `config_key_policy` table, seeded from this spec |
| 14 | //! by [`Database::seed_config_key_policy`](crate::Database::seed_config_key_policy) |
| 15 | //! and joined by the `user_config` triggers and the initial snapshot), and |
| 16 | //! - the Rust import filter ([`key_excluded_from_sync`]), applied when a remote |
| 17 | //! pull batch is written back. |
| 18 | //! |
| 19 | //! Storage stays sqlx (BB's `ConfigRepository`), not `synckit_config::ConfigStore` |
| 20 | //! (rusqlite): this crate takes only the pure-data half of synckit-config, the |
| 21 | //! spec and posture vocabulary, so a sqlx app shares the declaration without the |
| 22 | //! rusqlite store. Posture is fail-closed: an undeclared key is |
| 23 | //! [`Local`](Posture::Local) and never syncs. |
| 24 | //! |
| 25 | //! <!-- wiki: bb-config --> |
| 26 | |
| 27 | use ; |
| 28 | |
| 29 | /// BB's `user_config` posture declaration. |
| 30 | /// |
| 31 | /// Only `theme` crosses the sync boundary. `bb-welcomed` is a per-device |
| 32 | /// first-run flag (each device shows the welcome once). `bb-theme` is the legacy |
| 33 | /// theme key read once to migrate the old value into `theme`; it must not sync |
| 34 | /// on its own. Every other key the generic `set_config` command might write is |
| 35 | /// undeclared and therefore `Local` (fail-closed). |
| 36 | pub const CONFIG: ConfigSpec = new |
| 37 | "user_config", |
| 38 | & |
| 39 | // Synced: user preferences carried across the user's devices. |
| 40 | , |
| 41 | // Local: per-device state and the legacy alias, never synced. |
| 42 | , |
| 43 | , |
| 44 | ], |
| 45 | ; |
| 46 | |
| 47 | /// Import-side filter: true when a raw remote `user_config` key must be dropped |
| 48 | /// rather than written back. A key the spec does not clear to sync is refused, |
| 49 | /// which by the spec's fail-closed default also refuses any unknown key: a |
| 50 | /// hostile server cannot set a device-local or unrecognized key by sending it. |
| 51 | |
| 52 | |
| 53 | !CONFIG.is_synced |
| 54 | |
| 55 | |
| 56 | |
| 57 | |
| 58 | use *; |
| 59 | |
| 60 | |
| 61 | |
| 62 | assert!; |
| 63 | assert!; |
| 64 | assert! |
| 65 | !CONFIG.is_synced, |
| 66 | "the legacy alias never syncs" |
| 67 | ; |
| 68 | |
| 69 | |
| 70 | |
| 71 | |
| 72 | assert!; |
| 73 | assert! |
| 74 | key_excluded_from_sync, |
| 75 | "per-device stays home" |
| 76 | ; |
| 77 | assert! |
| 78 | key_excluded_from_sync, |
| 79 | "an unknown key fails closed", |
| 80 | ; |
| 81 | |
| 82 | |
| 83 | // The policy rows the export filter is seeded from must carry every declared |
| 84 | // key, marking only `theme` replicated. If this drifted, the SQL export and |
| 85 | // the Rust import would disagree. |
| 86 | |
| 87 | |
| 88 | let replicated: = CONFIG |
| 89 | .policy_rows |
| 90 | .filter |
| 91 | .map |
| 92 | .collect; |
| 93 | assert_eq!; |
| 94 | |
| 95 | |
| 96 |