# Delta for Native Alarms ## ADDED Requirements ### Requirement: Fail-Safe stopActive Semantics `PluriWaveAlarmService` MUST expose an id-agnostic stop action (`stopActive`), used by the ringing UI and the notification Stop action, that stops the currently-active ring regardless of the alarm id it is called with. It MUST NEVER silently no-op while a ring is active. On concurrent-alarm ambiguity, it MUST verify state and report the outcome, failing toward silencing the active ring. #### Scenario: Stop with mismatched id still silences the active ring **Testability**: [kotlin-static] - GIVEN `activeAlarmId = A` is ringing - WHEN `stopActive` is invoked with id B (mismatch) - THEN ring A is stopped, not silently ignored, and the caller gets a verified result #### Scenario: Stop with no active ring reports cleanly **Testability**: [kotlin-static] - GIVEN no alarm is currently ringing - WHEN `stopActive` is invoked - THEN it reports "nothing to stop" rather than throwing or hanging #### Scenario: Concurrent ambiguity fails toward silence **Testability**: [device-qa] - GIVEN two alarms could plausibly be "the active one" - WHEN `stopActive` resolves the ambiguity - THEN the active ring MUST be silenced (over-stop preferred over leaving it ringing) ### Requirement: Atomic Stop Coupling Every stop path MUST perform `stopForeground(STOP_FOREGROUND_REMOVE)`, `stopSelf()`, and wakelock release together, with no branch that performs a subset. #### Scenario: Stop path releases all three resources together **Testability**: [kotlin-static] - GIVEN any stop entry point in `PluriWaveAlarmService` - WHEN read/inspected - THEN all three teardown calls are reachable from it with no partial-teardown branch ### Requirement: Bounded Auto-Silence at 10 Minutes If a fired alarm is not stopped within 10 minutes, the system MUST transition it FIRED→MISSED via the atomic stop path, post a missed-alarm notification, rearm the next occurrence for repeating alarms, and leave one-shot alarms disabled. #### Scenario: Unattended ring auto-silences at 10 minutes **Testability**: [kotlin-static] + [device-qa] - GIVEN an alarm fires and is never stopped - WHEN 10 minutes elapse - THEN audio stops via the atomic stop path and a missed notification posts #### Scenario: Repeating alarm rearms after auto-silence **Testability**: [kotlin-static] - GIVEN a repeating alarm auto-silences - WHEN the MISSED transition completes - THEN `AlarmScheduler` arms the next occurrence #### Scenario: One-shot alarm disables after auto-silence **Testability**: [kotlin-static] - GIVEN a one-shot alarm auto-silences - WHEN the MISSED transition completes - THEN the alarm is left disabled, not rearmed ### Requirement: Durable Firing Record + Restart Re-Validation Before playback starts, the service MUST persist a durable "firing since T" record. `onStartCommand` MUST re-validate it on every entry, and the service MUST run `START_NOT_STICKY`. The record MUST clear only on a confirmed stop or a completed auto-silence transition. #### Scenario: Firing record persists before audio starts **Testability**: [kotlin-static] - GIVEN a fire event - WHEN `startAlarm` begins - THEN a durable record is written before `MediaPlayer.start()` #### Scenario: onStartCommand re-validates on restart **Testability**: [kotlin-static] - GIVEN the service process is killed and restarted by the OS - WHEN `onStartCommand` runs again - THEN it checks the record's age/state before resuming any audio action and returns `START_NOT_STICKY` #### Scenario: Confirmed stop clears the record **Testability**: [kotlin-static] - GIVEN a confirmed stop via `stopActive` - WHEN the atomic stop path completes - THEN the durable firing record is cleared ### Requirement: Boot/Restart Cleanup of Stale Firing Records On boot/unlock/package-replace, a durable firing record older than the 10-minute auto-silence bound MUST be treated as missed and cleaned up, not left dangling. #### Scenario: Stale record cleaned at boot **Testability**: [kotlin-static] - GIVEN a firing record older than 10 minutes exists at boot - WHEN `PluriWaveBootReceiver` runs `reschedulePersistedAlarms` - THEN the stale record is cleared and treated as a MISSED transition ### Requirement: P1 — FSI, Exact-Alarm, and Notification-Permission Fallbacks The system MUST call `canUseFullScreenIntent()` before relying on FSI and fall back to a heads-up notification when denied. It MUST show an in-app warning when exact-alarm scheduling permission is denied and when `POST_NOTIFICATIONS` is denied. #### Scenario: FSI unavailable falls back to heads-up **Testability**: [kotlin-static] - GIVEN `canUseFullScreenIntent()` returns false - WHEN a fire notification is built - THEN it posts as heads-up instead of FSI #### Scenario: Exact-alarm denial warns in-app **Testability**: [dart-testable] - GIVEN exact-alarm scheduling permission is denied - WHEN the user schedules/saves an alarm - THEN an in-app warning is shown #### Scenario: POST_NOTIFICATIONS denial warns in-app **Testability**: [dart-testable] - GIVEN `POST_NOTIFICATIONS` permission is denied - WHEN the user schedules an alarm - THEN an in-app warning is shown