ADR-0005: Capture gating and replay
- Status: Accepted
- Date: 2026-08-11
- Deciders: maintainers
- Related: ADR-0003
Context
Notifications come in high volume and much of it is noise (promo, OTP). The OS can also mask notification content (hidden bodies), and the listener can rebind/restart, re-delivering the same notifications.
Decision
A layered gate in the capture path:
- Keyword pre-filter (
NotificationFilter) drops promo/OTP noise (isExcluded) before parsing, and flags OS-masked content (isMaskedContent) so it is routed to the unmatched queue instead of being dropped. - Session-level dedupe: a bounded key set (
CapturePipeline.sessionSeen, max 512 keys, cleared when full) skips re-processing the same notification after a listener rebind. Memory-only; the DB unique index is the durable guarantee. - 48 h replay: on
onListenerConnected, the listener replays active notifications from the last 48 h (REPLAY_WINDOW_MILLIS) so nothing is lost across a restart. Replay is idempotent via dedupe.
Consequences
- Promo/OTP noise never lands in the review queue — it is dropped before parsing.
- Masked transaction content is preserved for manual review with
isMasked = truerather than silently dropped. - Restarts and rebinds are safe: duplicate inserts are a no-op (unique index
on
notificationKey). - Replay covers only notifications the system still holds as active — fully dismissed alerts may be missed (see known limitations in the README).
Alternatives considered
- Relying on the DB unique index alone without session dedupe — rejected: the session set avoids re-running the parser/classifier on rebind replays.
- No replay — rejected: force-stop/rebind gaps would lose notifications.