Skip to content

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:

  1. 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.
  2. 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.
  3. 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 = true rather 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.