testsAndMisc/docs/superpowers/contracts/focus-owner-service-layer-2026-08-09.json
Krzysztof kuhy Rudnicki cc804dd311 Run enforcement from exact alarms, and re-arm it before first unlock
The decision layer needed a runtime. A polling loop was the obvious design and
the wrong one: Doze can skip a poll indefinitely, and skipping the 05:00
boundary leaves the curfew allowlist applied into the morning -- the user finds
their banking app missing with no obvious cause. The transitions are known
ahead of time, so they get exact alarms. The service wakes, applies, schedules
the next run and stops rather than sitting resident.

BootReceiver takes LOCKED_BOOT_COMPLETED as well as BOOT_COMPLETED, because the
latter does not arrive until first unlock. That is not theoretical: this phone
sat at RUNNING_LOCKED for minutes after an earlier reboot in this session, with
no network and no app able to start. Relying on BOOT_COMPLETED alone freezes
enforcement state across a reboot -- safe for hiding, not for unhiding, which is
the direction that strands you without a dialer. The schedule is re-armed
before unlock; the pass itself waits until storage is readable.

The first on-device run logged "exact alarms not permitted", and dumpsys showed
what that costs: window=+11m14s, maxWhenElapsed=+25m51s, so a 15-minute
fallback could slip to 26 minutes. SCHEDULE_EXACT_ALARM is user-revocable and
denied by default on Android 16; USE_EXACT_ALARM is the permission for apps
whose function is acting at specific times, and is granted at install. After
the change: window=0, exactAllowReason=policy_permission, whenElapsed equal to
maxWhenElapsed.

The service reports "not DO/PO - no enforcement applied" and does nothing when
unprovisioned, rather than failing quietly, and a failure inside a pass cannot
stop the next run being scheduled -- an enforcement bug should degrade one tick,
not end the chain.

Not yet wired: the service logs a tick rather than calling setApplicationHidden
with the decision. Both halves are tested; joining them is next.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AwepyL5Lxs5QKbKkMpxxxL
2026-08-09 21:50:58 +02:00

20 lines
1.4 KiB
JSON

{
"title": "Alarm-driven enforcement service that survives reboot and Doze",
"objective": "Give the enforcement decision a runtime: something that applies it at the moments it can change, survives a reboot, and is not silently deferred by Doze. Success is a service that runs, arms its own next run, fires exactly at scheduled times, and refuses to act when not provisioned rather than failing quietly.",
"acceptance_criteria": [
"The service applies one pass then stops; it is not resident and does not poll",
"Curfew boundaries fire exactly - window=0 in dumpsys alarm, not a multi-minute window",
"A reboot re-arms the schedule even before the user unlocks",
"The service reports and does nothing when neither device owner nor profile owner, rather than failing silently",
"A failure inside a pass cannot stop the next run being scheduled",
"packagesToShow is applied unconditionally each pass so a missed run self-repairs"
],
"out_of_scope": [
"Device Owner provisioning - the service correctly no-ops without it",
"The actual hide/show application: the decision layer and the DPM call both exist, wiring them together is next",
"Location and workout providers - interfaces exist, platform plumbing does not",
"DNS filtering, still parked"
],
"verifier": "adb install + am start; adb logcat for the boot and service lines; dumpsys alarm to confirm window=0 and exactAllowReason; flutter analyze && flutter test"
}