Alert Flow Review
Last updated: 2026-04-02 09:30 EDT Status: current implementation review baseline Owner: BionicLoop engineering
Purpose
This document is the review-oriented companion to Alert Inventory and Mapping.
Use it when you want to inspect the implemented alert flow end-to-end:
- what triggers an alert
- how it is normalized into
AppAlert - where it is presented to the user
- how it clears, retracts, or requires acknowledgement
Scope
This document covers the current implemented alert path for:
- pump-native alerts
- pump synthesized state alerts
- CGM state-driven and fallback alerts
- runtime interruption alerting
- app-policy alerts
This document does not treat preview-only alert injection as production behavior.
Defined in AppAlertCode but not currently raised by production alert flows:
ALERT-LOOP-AWAITING-FIRST-STEPALERT-LOOP-COMMAND-BLOCKEDALERT-MEAL-NOT-AVAILABLE-NOW
Those codes currently exist for preview/test support and routing definitions, but not for live user alert issuance.
End-to-End Flow
flowchart LR
A["Pump / CGM / Runtime / App Policy Trigger"] --> B["Mapper / Monitor creates AppAlert or retracts dedupe key"]
B --> C["AppAlertCenter"]
C --> D["Active Alerts (persisted)"]
C --> E["Recently Cleared Timeline (persisted)"]
C --> F["Background Local Notification (severity + cooldown gated)"]
D --> G["Home Alert Carousel / Banner"]
D --> H["Home Bell Badge"]
D --> I["Alert Center Active List"]
E --> J["Alert Center Recently Cleared List"]
F --> K["Notification Tap Routing"]
Shared Review Rules
| Review topic | Current behavior | Primary implementation |
|---|---|---|
| Canonical model | All live alerts are normalized to AppAlert with source, sourceCode, normalizedCode, severity, message, recommendedAction, ackState, dedupeKey, and optional countdown metadata. |
BionicLoop/Runtime/AppAlertSupport.swift, BionicLoop/Runtime/AppAlertDomainSupport.swift |
| Ordering / top-alert selection | Active alerts are ordered by severity first, then timestamp, then dedupe key. Home always shows the current top alert first. | AppAlertPrioritizer in BionicLoop/Runtime/AppAlertSupport.swift |
| Dedupe / replace behavior | AppAlertCenter replaces an existing active alert when the dedupe key matches; otherwise it appends a new active alert. |
AppAlertCenter.upsertNormalizedAlert(_:) |
| Clear / retract behavior | Retract removes the active alert, clears any pending/delivered OS notification, and appends a ClearedAppAlert entry to the recently-cleared timeline. |
AppAlertCenter.retractNormalizedAlert(dedupeKey:) |
| Acknowledge behavior | Only ackState == .requiresAcknowledge exposes an explicit acknowledge button. Most required-ack alerts clear on acknowledge, record reason acknowledged, and propagate closure back into the source lifecycle store through a source-specific handler. ALERT-CGM-URGENT-LOW is the current exception: acknowledge stamps clinician-review state (acknowledgedAt) but leaves the alert active until trustworthy glucose recovery clears it. If a source handler is not registered yet during startup, AppAlertCenter persists a pending acknowledgement request and replays it when the handler registers later. |
AppAlertCenter.acknowledgeAlert(dedupeKey:), HomeAlertBannerView, AlertCenterView |
| Background notification channel | Only non-CGM actionable and safetyCritical alerts can schedule background local notifications, and only when the app is backgrounded. Cooldown is 5 minutes for actionable, 60 seconds for safetyCritical. CGM alerts are in-app informational only. |
AppAlertCenterNotificationSupport, UserNotificationAppAlertScheduler |
| Notification tap routing | Notification tap routes by normalized code: pump alerts -> pump modal, CGM alerts -> CGM modal, app/runtime alerts -> Home. The CGM route remains defined for in-app navigation/preview support even though production CGM alerts do not schedule background notifications. | AppAlertNotificationRouter in BionicLoop/App/BionicLoopApp.swift |
| Persistence | AppAlertCenter persists active alerts and recently-cleared alerts across relaunch. Pump/CGM delegate lifecycle stores preserve issued/unretracted/retracted source alerts and restore them on attach. |
AppAlertCenterPersistenceSupport, AppPumpManagerDelegate, AppCGMManagerDelegate |
| Time-sensitive refresh | Countdown-backed alerts refresh displayed message text once per minute while active. Current production countdown alerts are ALERT-PUMP-EXPIRING and ALERT-PUMP-EXPIRED. |
AppAlertCenter.refreshTimeSensitiveAlerts() |
| Telemetry | Alert issue/update, retract/acknowledge, notification schedule/clear/tap are all emitted as telemetry events. | AlertLifecycleTelemetryEventMapper, UserNotificationAppAlertScheduler, AppAlertNotificationRouter |
User-Visible Presentation Surfaces
| Surface | What the user sees | File path | Review note |
|---|---|---|---|
| Home top alert | Severity-colored banner for the current top alert. If multiple alerts are active, Home shows a vertical carousel with count and step-through controls. | BionicLoop/Features/Home/HomeAlertCenterView.swift |
This is the main foreground alert surface. |
| Home bell badge | Bell button with active-alert count badge. | BionicLoop/Features/Home/HomeTopOverlayBar.swift |
Fast path to Alert Center. |
| Settings entry | Alert Center row in Settings with active-alert count. |
BionicLoop/Features/Home/HomeSettingsNavigationSectionsView.swift |
Secondary path to Alert Center. |
| Alert Center active list | Sorted active alerts, including acknowledge button when required. | BionicLoop/Features/Home/HomeAlertCenterView.swift |
Review active ordering and acknowledge affordance here. |
| Alert Center recently-cleared list | Timeline of cleared alerts with pagination (More / Less). |
BionicLoop/Features/Home/HomeAlertCenterView.swift |
Review closure behavior here. |
| Background local notification | OS notification for background non-CGM actionable / safetyCritical alerts. |
BionicLoop/Runtime/UserNotificationAppAlertScheduler.swift |
Review cooldown, dedupe, and tap routing here. |
Source Flow Review
Pump Alert Flows
Pump alerts come from two paths:
- native LoopKit/Omni alerts entering
AppPumpManagerDelegate.issueAlert(_:)/retractAlert(identifier:), normalized byPumpAlertMapper - synthesized pump-state conditions entering
AppAlertCenter.updatePumpSignalLossCondition(...)andupdatePumpNoActivePodCondition(...)through Home/runtime pump-state evaluation
Pump lifecycle state is also restored on delegate attach from the persisted alert store, and pod-expiration state can be synthesized from current pump status so relaunch does not miss expiration/grace-window alerts.
| Normalized code | Trigger to issuance | Presentation to user | Close / clear behavior | Primary implementation |
|---|---|---|---|---|
ALERT-PUMP-SIGNAL-LOSS |
Armed loop, active pod, established pump session, and missing/unknown pump status for longer than the 5 minute debounce window. |
Home top alert/carousel, Alert Center, Home bell count, background local notification when app is backgrounded. Notification tap routes to pump. | Auto-clears when the condition clears. Retract also clears matching OS notification. | PumpSignalLossAlertEvaluator, HomeActionCoordinator.applyPumpAlertPresentation, AppAlertCenter.updatePumpSignalLossCondition(...) |
ALERT-PUMP-NO-ACTIVE-POD |
Armed loop, first successful step exists, and hasActivePod == false for longer than the 5 minute debounce window. |
Same user surfaces as other active pump alerts; routes to pump when notification is tapped. | Auto-clears once an active pod is restored. No-active-pod cleanup retracts only non-critical pod-tied alerts. | PumpNoActivePodAlertEvaluator, HomeActionCoordinator.applyPumpAlertPresentation, AppAlertCenter.updatePumpNoActivePodCondition(...) |
ALERT-PUMP-LOW-RESERVOIR |
LoopKit pump alert lowReservoir. |
Home, Alert Center, background local notification when app is backgrounded; notification tap routes to pump. | Auto-clears on lifecycle retraction / recovery. | AppPumpManagerDelegate.issueAlert(_:), PumpAlertMapper.lowReservoirAlert(...) |
ALERT-PUMP-EXPIRING |
LoopKit userPodExpiration, or state-driven synthesis from expiresAt when pod is within 1 hour of expiry and still in the future. |
Home, Alert Center, background local notification; notification tap routes to pump. Message refreshes as countdown advances. | Auto-clears on lifecycle retraction / recovery or when replaced by expired state. | PumpAlertMapper.expiringAlert(...), pod-expiration sync support, AppAlertCenter.refreshTimeSensitiveAlerts() |
ALERT-PUMP-EXPIRED |
LoopKit podExpiring / podExpireImminent, or state-driven synthesis when expiresAt has passed and pod still has remaining post-expiration service time. |
Home, Alert Center, background local notification; notification tap routes to pump. Message refreshes through the remaining delivery-stop countdown after nominal expiration. | Auto-clears on lifecycle retraction / recovery, once pod service time is exhausted, or when pod is detached. | PumpAlertMapper.expiredAlert(...), pod-expiration sync support, AppAlertCenter.refreshTimeSensitiveAlerts() |
ALERT-PUMP-FAULT |
Pump fault/alarm signal or unexpectedAlert mapping. |
Home, Alert Center, background local notification. Requires explicit acknowledge in foreground UI. Notification tap routes to pump. | Does not auto-clear solely because there is no active pod. Clears on lifecycle retraction/recovery or explicit acknowledge. Required-ack closure is now written back to the persisted pump alert store so the alert does not restore on relaunch once acknowledged. | PumpAlertMapper.faultAlert(...), AppAlertCenter.acknowledgeAlert(dedupeKey:), AppPumpManagerDelegate |
ALERT-PUMP-INCOMPATIBLE |
Incompatible pod signal/text in pump alert mapping. | Same as pump fault: Home, Alert Center, background local notification, acknowledge required, notification tap routes to pump. | Same retention rule as pump fault, including persisted acknowledge closure preventing relaunch restore after the user acknowledges it. | PumpAlertMapper.incompatibleAlert(...), AppAlertCenter.acknowledgeAlert(dedupeKey:), AppPumpManagerDelegate |
ALERT-PUMP-SETUP-INCOMPLETE |
LoopKit finishSetupReminder. |
Home, Alert Center, background local notification when app is backgrounded; notification tap routes to pump. | Auto-clears on lifecycle retraction / setup completion. | PumpAlertMapper.setupIncompleteAlert(...) |
ALERT-PUMP-SUSPEND-IN-PROGRESS |
LoopKit suspendInProgress. |
Home, Alert Center, background local notification; notification tap routes to pump. | Auto-clears on lifecycle retraction / delivery resume. | PumpAlertMapper.suspendInProgressAlert(...) |
ALERT-PUMP-SUSPEND-ENDED |
LoopKit suspendEnded. |
Home, Alert Center, background local notification; notification tap routes to pump. | Auto-clears on lifecycle retraction / recovery. | PumpAlertMapper.suspendEndedAlert(...) |
ALERT-PUMP-TIME-OFFSET-DETECTED |
LoopKit timeOffsetChangeDetected. |
Home, Alert Center, background local notification; notification tap routes to pump. | Auto-clears on lifecycle retraction / recovery. | PumpAlertMapper.timeOffsetDetectedAlert(...) |
CGM Alert Flows
CGM alerts are intentionally state-driven first. AppCGMManagerDelegate.syncG7Alerts(for:) rebuilds current CGM alert state from live AlgorithmState and G7SensorLifecycleState. LoopKit issueAlert fallback mapping remains in place for compatibility and persisted-alert plumbing, but it does not drive failed/expired escalation from message-only payload text.
Operational policy: the FDA-cleared Dexcom application is the source of truth for CGM alarming. BionicLoop CGM availability/failure alerts are informational in-app state only on Home / Alert Center and do not schedule OS notifications. BionicLoop also derives an in-app urgent-low review alert from trustworthy G7 glucose <55 mg/dL; that alert is for clinician review/telemetry only, does not represent Dexcom-app acknowledgement, and still does not schedule OS notifications.
| Normalized code | Trigger to issuance | Presentation to user | Close / clear behavior | Primary implementation |
|---|---|---|---|---|
ALERT-CGM-UNAVAILABLE |
Live G7 state reports temporary error, warmup, or no reliable glucose; fallback LoopKit keyword mapping only classifies unavailable-temporary states. | Home, Alert Center. No background local notification is scheduled by BionicLoop. | Auto-clears once live CGM state recovers; restored from live state on attach rather than replayed from persisted LoopKit alert payloads. | AppCGMManagerDelegate.syncG7Alerts(for:), CGMAlertMapper.normalizedG7Alert(...), CGMAlertMapper.normalizedAppAlert(...) |
ALERT-CGM-FAILED-OR-EXPIRED |
Live G7 lifecycle is failed / expired, or live algorithm state reports failed / expired. Fallback LoopKit mapping only classifies failed/expired from source identifiers, not free-text message keywords. |
Home, Alert Center. No background local notification is scheduled by BionicLoop and no app-side acknowledge button is shown. Operators are expected to rely on the Dexcom app for CGM alarming. | Clears on live-state recovery/replacement. Not replayed from persisted LoopKit payloads on attach. | AppCGMManagerDelegate.syncG7Alerts(for:), CGMAlertMapper.normalizedG7Alert(...), CGMAlertMapper.normalizedAppAlert(...) |
ALERT-CGM-URGENT-LOW |
Trustworthy live G7 reading <55 mg/dL (hasReliableGlucose == true and fresh timestamp) from current state sync. This is app-derived from CGM data and mirrors the Dexcom urgent-low threshold for clinician review, but it is not Dexcom-app acknowledgement. |
Home, Alert Center. No background local notification is scheduled by BionicLoop. Active alert can show reviewed state after acknowledge. | Acknowledge records clinician-review state and telemetry but does not clear the active alert while glucose remains <55. Active alert auto-clears only on a later trustworthy reading >=55 mg/dL; stale, unreliable, or missing readings do not clear the episode. Acknowledged active state persists across reset / delegate reattach until recovery clears the episode. |
AppCGMManagerDelegate.syncG7Alerts(for:), CGMAlertMapper.normalizedUrgentLowAlert(...), AppAlertCenter.acknowledgeAlert(dedupeKey:), CGMAlertPersistenceStore |
Runtime Alert Flow
Currently the only production runtime-issued user alert is the step-interruption alert.
| Normalized code | Trigger to issuance | Presentation to user | Close / clear behavior | Primary implementation |
|---|---|---|---|---|
ALERT-ALGORITHM-STEPPING-INTERRUPTED |
Armed loop has not completed a successful algorithm step before the computed interruption deadline. LoopRuntimeEngine recomputes the monitoring decision after execution publication and on state refresh; AppAlertCenter schedules a future local notification for the deadline and upserts the alert when the deadline passes. |
Home, Alert Center, background local notification. Notification tap routes to Home. Remains distinct from informational CGM state surfaces. | Clears when successful stepping resumes, when the loop is disarmed/reset, or when monitoring is disabled. Pending future notification is cleared when monitoring is disabled or replaced. | LoopRuntimeEngine.refreshAlgorithmSteppingInterruptionMonitoring(), LoopRuntimeEngineStepInterruptionMonitoringSupport, AppAlertCenter.updateAlgorithmSteppingInterruptionMonitoring(...) |
App-Policy Alert Flows
These alerts are not device-native; they come from app/session policy and cloud-facing workflow checks.
| Normalized code | Trigger to issuance | Presentation to user | Close / clear behavior | Primary implementation |
|---|---|---|---|---|
ALERT-AUTH-LOGIN-REQUIRED |
ContentView.syncAuthRecoveryAlert() determines the user is unauthenticated while an active algorithm state remains and remote-monitoring login recovery is required. |
Home, Alert Center, background local notification when app is backgrounded. Home banner exposes primary action Log In. Notification tap routes to Home. |
Clears when the user re-authenticates or when there is no longer an active algorithm state requiring recovery. | AppAuthRecoveryPolicy, ContentView.syncAuthRecoveryAlert() |
ALERT-SUBJECT-ID-CONFLICT |
CloudTelemetryReporter detects a permanent subject-claim conflict from the telemetry/device-cloud path and upserts the normalized alert. |
Home, Alert Center, background local notification when app is backgrounded. Notification tap routes to Home. | Clears when the operator successfully saves corrected clinical settings after subject claim succeeds, and also clears when Home later re-validates the currently persisted subject ID on launch, foreground, or settings reopen and the backend claim now succeeds; both paths retract the normalized alert dedupe key. | CloudTelemetryReporter.subjectIDConflictAlert(timestamp:), HomeSettingsView.confirmSaveReview(_:), HomeView.recheckSubjectIDConflictIfNeeded(now:) |
ALERT-APP-CLOCK-SKEW |
DeviceClockSyncMonitor performs a UTC drift check on launch, foreground (when last success is older than 24 hours), or time/timezone change. If abs(skew_seconds) > 600, it issues the alert, limited to once per 24 hours. |
Home, Alert Center, background local notification when app is backgrounded. Notification tap routes to Home. | Auto-clears when a later successful check returns ok. Unavailable checks do not raise or spam alerts. |
DeviceClockSyncMonitor.applyWarningPolicy(...) |
Alert Center Review Notes
Use the Alert Center to review lifecycle behavior, not just current foreground presentation.
Activesection shows all active normalized alerts sorted by severity, then recency.Recently Clearedsection shows the clear history sorted byclearedAtdescending.requiresAcknowledgealerts expose acknowledge affordance in both Home banner/carousel and the active Alert Center row.- Recently-cleared retention is capped at
100entries in persistence. - Recently-cleared UI pagination is
10rows at a time withMore/Less.
Primary files:
BionicLoop/Features/Home/HomeAlertCenterView.swiftBionicLoop/Runtime/AppAlertCenter.swiftBionicLoop/Runtime/AppAlertCenterPersistenceSupport.swift
Background Notification Review Notes
Review background alert behavior separately from foreground UI.
- Notifications are scheduled only for
actionableandsafetyCriticalalerts. - Informational alerts do not schedule background notifications.
- Category identifier is the normalized alert code.
- Notification identifier is derived from the dedupe key, so retract/replace paths clear the matching OS notification.
- Safety-critical notifications use iOS time-sensitive interruption level when available.
- Notification tap routing is by normalized alert code:
- pump -> pump modal
- CGM -> CGM modal
- app/runtime -> Home
Primary files:
BionicLoop/Runtime/UserNotificationAppAlertScheduler.swiftBionicLoop/App/BionicLoopApp.swift
Persistence and Relaunch Review Notes
AppAlertCenterpersists both active alerts and recently-cleared alerts inUserDefaults.- Pump and CGM delegate persisted-alert stores preserve issued/unretracted/retracted source alerts across relaunch.
- On delegate attach:
- pump alerts are restored from unretracted, unacknowledged persisted alerts
- CGM state-driven alerts are rebuilt from current live G7 state, not replayed from stale persisted payloads
- No-active-pod relaunch cleanup retracts only non-critical pod-bound alerts;
ALERT-PUMP-FAULTandALERT-PUMP-INCOMPATIBLEremain until explicit closure. - Explicit acknowledge of required-ack pump alerts now persists closure into the pump lifecycle store, so acknowledged fault/incompatible alerts do not rehydrate on delegate reattach.
- If a required-ack pump alert is acknowledged before the pump acknowledgement handler registers on startup,
AppAlertCenterpersists that pending acknowledgement locally and the pump delegate drains it before computing restore candidates. - If
ALERT-CGM-URGENT-LOWis acknowledged before the CGM acknowledgement handler registers on startup,AppAlertCenterpersists that pending acknowledgement locally and the CGM delegate drains it before rebuilding live urgent-low presentation.
Current Verification Anchors
Primary trace anchors for this flow:
- Requirements: SRS-ALERT-001..016
- Design: SDD-ALERT-001, SDD-POL-008, SDD-POL-026
- Verification inventory: TV-ALERT-001..015
- Execution protocol: STP-ALERT-001
Useful direct review evidence already called out in the SVVP:
testSignalLossDebounceAddsAndClearsAlerttestSignalLossDebounceSuppressesTransientConditiontestAlertCenterTracksRecentlyClearedAlertstestAlertCenterRestoresPersistedActiveAndClearedAlertstestPumpPersistedAlertStoreReturnsIssuedAndRetractedAlertstestCGMPersistedAlertStoreReturnsIssuedAndRetractedAlertstestCGMUrgentLowAcknowledgePersistsAcrossAlertCenterResetUntilRecoverytestPumpExpirationAlertSyncPlannerReturnsRetractsWhenNoExpirationAlertsApplytestTimeSensitivePumpExpiringAlertRefreshesMessageWithoutReschedulingNotificationtestCGMAlertsNeverScheduleBackgroundNotificationstestUI007_HomeAlertCenterButtonOpensAlertCentertestUI008_AlertCenterAcknowledgeMovesAlertToRecentlyClearedtestUI009_AlertCenterPersistsAcrossRelaunch
Review Outcome Summary
As of this baseline:
- the implemented production alert path is unified through
AppAlertCenter - the main user-visible channels are Home foreground presentation, Alert Center, and background local notifications for non-CGM alerts
- pump, CGM, runtime interruption, and app-policy alert flows are all reviewable from source trigger through user presentation
- three alert codes remain defined but not currently issued by production flows:
ALERT-LOOP-AWAITING-FIRST-STEPALERT-LOOP-COMMAND-BLOCKEDALERT-MEAL-NOT-AVAILABLE-NOWALERT-SUBJECT-ID-CONFLICTnow has both an issue path from telemetry conflict handling and retract paths in the successful Clinical Settings save flow plus the Home auto-revalidation flow for stale conflicts