strategy: make pushed config authoritative

handle_config_options only ever inserted/overwrote the keys a strategy
sent, so removing a key (or unassigning a strategy) on the server left the
old value lingering on the device.

Persist the set of keys applied on the previous push (strategy_managed_keys
in LocalConfig) and, on each apply, reset any key the server managed before
but no longer sends back to its default. Keys the server never managed (the
user's own local settings) are left untouched, so this remains a managed
overlay rather than a wipe.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-16 08:54:12 +00:00
parent 31d5a881b6
commit faf3b1303b
+27
View File
@@ -413,9 +413,28 @@ fn heartbeat_url() -> String {
format!("{}/api/heartbeat", url)
}
/// LocalConfig key holding the JSON list of option keys the server's strategy
/// applied on the previous push, so we can make strategies *authoritative*.
const STRATEGY_MANAGED_KEYS: &str = "strategy_managed_keys";
fn handle_config_options(config_options: HashMap<String, String>) {
let mut options = Config::get_options();
let default_settings = config::DEFAULT_SETTINGS.read().unwrap().clone();
// hello-agent local patch — authoritative strategies. The server's strategy
// owns exactly the keys it sends. Any key it managed on the previous push
// but no longer sends is reset to its default (the override is dropped), so
// removing a key/strategy on the server actually clears it on the device.
// Keys the server never managed (the user's own local settings) are left
// untouched, so this stays a managed-config overlay rather than a wipe.
let prev_managed: Vec<String> =
serde_json::from_str(&LocalConfig::get_option(STRATEGY_MANAGED_KEYS)).unwrap_or_default();
for k in prev_managed.iter() {
if !config_options.contains_key(k) {
options.remove(k);
}
}
config_options
.iter()
.map(|(k, v)| {
@@ -429,6 +448,14 @@ fn handle_config_options(config_options: HashMap<String, String>) {
}
})
.count();
// Remember the keys we now manage, for the next diff.
let managed: Vec<&String> = config_options.keys().collect();
LocalConfig::set_option(
STRATEGY_MANAGED_KEYS.to_string(),
serde_json::to_string(&managed).unwrap_or_default(),
);
Config::set_options(options);
}