// DragonX Wallet - ImGui Edition // Copyright 2024-2026 The Hush Developers // Released under the GPLv3 // // Lite-wallet rollout / kill-switch policy. // // A pure decision core that gates whether the lite wallet may run, given a local override, an // emergency env kill-switch, an optional rollout manifest, the running app version, and a stable // per-install rollout bucket. Privacy posture (this is a privacy coin): the manifest is read from a // LOCAL cache file only — there is NO runtime network fetch (a signed remote fetcher can populate // that cache later without touching this policy), it is consulted FAIL-OPEN (absent/invalid → // allowed, so a missing file never bricks a working install), and the install bucket is derived // from a locally-generated random id that is never transmitted and carries no PII. #pragma once #include #include namespace dragonx::wallet { // User/support local override (persisted in settings as "lite_rollout"). enum class LiteRolloutOverride { Auto, // honor the manifest / staged rollout ForceOn, // always enable; bypasses the manifest + staged rollout, but NOT the env kill-switch ForceOff // always disable }; enum class LiteRolloutStatus { Allowed, DisabledByKillSwitchEnv, // DRAGONX_LITE_KILL_SWITCH=1 (emergency, absolute) DisabledByLocalOverride, // lite_rollout=force_off DisabledByManifest, // manifest global_enabled=false DisabledUnsupportedVersion, // version below min / above max supported DisabledBlockedVersion, // version explicitly blocked DisabledByStagedRollout // install bucket above the rollout threshold }; // Rollout manifest. Loaded from a local cache file; present=false means no manifest (fail-open). struct LiteRolloutManifest { bool present = false; // a manifest file was found bool valid = false; // it parsed and validated bool globalEnabled = true; // master enable switch std::string minVersion; // inclusive floor (empty = none) std::string maxVersion; // inclusive ceiling (empty = none) std::vector blockedVersions; int rolloutPermille = 1000; // 0..1000; install allowed when bucket < permille (1000 = 100%) std::string message; // user-facing reason shown when disabled }; struct LiteRolloutInputs { LiteRolloutOverride override = LiteRolloutOverride::Auto; bool killSwitchEnv = false; LiteRolloutManifest manifest; std::string appVersion; int installBucket = 0; // 0..999, stable per install }; struct LiteRolloutDecision { bool allowed = true; LiteRolloutStatus status = LiteRolloutStatus::Allowed; std::string message; // user-facing (empty when allowed) }; // The pure decision. No I/O. Evaluation order: env kill-switch (absolute) → local override → // manifest gates (global / version / blocked / staged rollout) → fail-open allow. LiteRolloutDecision evaluateLiteRollout(const LiteRolloutInputs& inputs); // Stable bucket 0..999 from an install id (FNV-1a). Deterministic; empty id -> 0. int liteRolloutBucketFromInstallId(const std::string& installId); // Compare dotted version cores, ignoring any "-suffix"/"+build". Returns -1 / 0 / 1. int compareLiteVersions(const std::string& a, const std::string& b); // Override <-> string ("auto"/"force_on"/"force_off"); unknown parses to Auto. LiteRolloutOverride liteRolloutOverrideFromString(const std::string& value); const char* liteRolloutOverrideToString(LiteRolloutOverride override); const char* liteRolloutStatusName(LiteRolloutStatus status); // Load + validate a manifest from a JSON file. Missing file -> {present=false}; parse error -> // {present=true, valid=false}. Both are fail-open at evaluation time. LiteRolloutManifest loadLiteRolloutManifestFromFile(const std::string& path); } // namespace dragonx::wallet