feat(lite): runtime kill-switch + staged-rollout gate (M5b)
Adds a fail-open, local-only gate that decides whether the lite wallet may run,
so a post-release issue can disable it and rollout can be staged — without any
phone-home (privacy posture: no runtime network fetch; the per-install rollout
bucket is a hashed, never-transmitted local id).
- wallet/lite_rollout_policy.{h,cpp}: a pure decision core. Order — emergency env
kill-switch (absolute) -> local override -> manifest gates (global enable /
version floor-ceiling / blocklist / staged-rollout permille) -> fail-open allow.
Plus a JSON manifest loader (missing/invalid -> fail-open) and FNV-1a bucketing.
- Threads the decision through LiteWalletController -> LiteWalletLifecycleService:
new availability() reason RolloutDisabled blocks create/open/restore and surfaces
the gate's user-facing message via the lifecycle status.
- App::rebuildLiteWallet() resolves it from: DRAGONX_LITE_KILL_SWITCH (env), the
lite_rollout setting (auto/force_on/force_off), and a locally-cached manifest at
<config-dir>/lite_rollout.json. install id generated once via libsodium.
- Settings: persist lite_rollout override + the install id.
A signed remote fetcher can populate the manifest cache later without touching the
policy. Unit-tested (version compare, bucketing, override/env precedence, manifest
gates, staged rollout, loader fail-open, controller integration) and runtime-verified
on Linux (env kill-switch, manifest disable, control sync). Both variants build;
full suite passes; hygiene clean.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -222,6 +222,13 @@ bool Settings::load(const std::string& path)
|
||||
lite_servers_.push_back(preference);
|
||||
}
|
||||
}
|
||||
if (lite.contains("rollout_override") && lite["rollout_override"].is_string()) {
|
||||
const auto v = lite["rollout_override"].get<std::string>();
|
||||
lite_rollout_override_ = (v == "force_on" || v == "force_off") ? v : "auto";
|
||||
}
|
||||
if (lite.contains("install_id") && lite["install_id"].is_string()) {
|
||||
lite_install_id_ = lite["install_id"].get<std::string>();
|
||||
}
|
||||
}
|
||||
if (j.contains("verbose_logging")) verbose_logging_ = j["verbose_logging"].get<bool>();
|
||||
if (j.contains("debug_categories") && j["debug_categories"].is_array()) {
|
||||
@@ -355,6 +362,8 @@ bool Settings::save(const std::string& path)
|
||||
entry["enabled"] = server.enabled;
|
||||
lite["servers"].push_back(entry);
|
||||
}
|
||||
lite["rollout_override"] = lite_rollout_override_;
|
||||
lite["install_id"] = lite_install_id_;
|
||||
j["lite_wallet"] = lite;
|
||||
}
|
||||
j["verbose_logging"] = verbose_logging_;
|
||||
|
||||
Reference in New Issue
Block a user