fix: enforce uninstall protection in background and lock its toggle during hard sessions#5
Merged
Conversation
…uring hard sessions Two defects in Uninstall Protection: 1. Background gap — app-removal denial was only recomputed on the foreground path (RuleEnforcer.refresh), so a Hard Mode window that started or ended while the app was closed left denyAppRemoval out of sync (an escape hatch on start; stuck-on after end). 2. The Settings toggle could be turned off mid-block, defeating the feature. Changes: - Add AppGroup.uninstallProtectionKey so the extensions can read the opt-in; point AppSettingsStore at it. - Add snapshot-based UninstallProtectionPolicy (mirrors RulePolicy's active/hard-locked semantics, including scheduled-today for limit rules; a parity unit test guards against drift) and UninstallProtectionEnforcer. Call reconcile() from the DeviceActivity monitor (interval start/end, usage threshold) and ShieldAction (after a granted open) so denial tracks hard blocks even while the app is closed. - Lock the Settings toggle while any Hard Mode rule is actively blocking: the switch is replaced by a red lock (mirrors Home's "Currently Blocking" rows) via RulePolicy.canToggleUninstallProtection, with an explanatory notice; guard the binding setter as defense in depth. - Update RULES_FEATURE_SPEC §6/§6.1; add unit + UI tests (218 passing). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…-and-lock Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Fixes two defects in Uninstall Protection (Settings → "Uninstall Protection", which denies device app removal while a Hard Mode rule is actively blocking, so a hard block can't be escaped by uninstalling OpenAppLock):
RuleEnforcer.refresh), so a Hard Mode window that started while the app was closed left the device removable (an escape hatch defeating the feature), and one that ended left protection stuck on until the next foreground. This was documented as a known v1 limitation in the spec.What changed
AppGroup.uninstallProtectionKeyso the Screen Time extensions can read the opt-in;AppSettingsStorepoints at it.UninstallProtectionPolicy(inShared/) mirrorsRulePolicy's active/hard-locked semantics exactly (including the scheduled-today check for limit rules). A parity unit test asserts it never drifts fromRulePolicy.UninstallProtectionEnforcer.reconcile()recomputes denial from the snapshots + opt-in. Called from the DeviceActivity monitor (interval start/end, usage threshold) and the ShieldAction extension (after a granted open), so denial tracks hard blocks even while the app is closed.ShieldApplyingis left unchanged.lock.fill(mirroring the Home "Currently Blocking" treatment) with an explanatory notice, gated byRulePolicy.canToggleUninstallProtection. The binding setter is also guarded as defense in depth.RULES_FEATURE_SPEC.md§6 / §6.1 updated to document both paths and the locked toggle.Test plan
RulePolicyparity, background enforcer reconcile,canToggleUninstallProtection.SettingsUITests.testUninstallProtectionLockedDuringHardSession(switch hidden + lock/notice shown under thehard-mode-activescenario); existing flip test still passes.denyAppRemovaltransition when a hard window starts/ends with the app closed is only observable on a real device (the simulator uses mock shields and delivers no DeviceActivity callbacks).🤖 Generated with Claude Code