notifications: full lifecycle (expiry, replaces_id, sender-loss, bounded, action cleanup)

The 5-lane review flagged that the notifications daemon had five
missing lifecycle behaviors. This commit implements all of them:

1. Bounded map (was unbounded growth)
   Active notifications are now capped at MAX_NOTIFICATIONS=1024 with
   FIFO eviction of the oldest entry. sender_to_ids index is also
   pruned on eviction.

2. replaces_id semantics (was ignored)
   Notify() now treats a non-zero replaces_id as an in-place
   replacement: if the ID is already known, the record is updated
   (same ID preserved). Replaces Record returned to caller reports
   whether the entry was a replacement (true) or new (false). Per
   freedesktop spec, the same notification ID survives.

3. Expire-timeout sweep (was ignored)
   NotificationRecord now carries expires_at: Option<Instant>, set
   from expire_timeout. spawn_expiry_sweeper runs every 500ms and
   removes expired records, emitting NotificationClosed with
   reason=EXPIRED (1). expire_due_notifications() returns the IDs
   for the background sweep.

4. Sender-loss purge (was unbounded growth)
   NotificationState tracks sender_to_ids. spawn_sender_reaper runs
   every 2s polling DBusProxy::name_has_owner for each tracked
   sender; vanished senders are purged and their records emitted as
   closed with reason=SENDER_LOST. Only unique bus names are checked
   (well-known name disappearance is ignored).

5. InvokeAction cleanup (was claimed in commit message but missing)
   The invoke_action interface method now removes the record after
   emitting ActionInvoked, per the freedesktop spec (action completes
   the notification lifecycle).

State model refactored: Notifications now wraps an Arc<NotificationState>
containing the maps/queues, so background tasks share state with the
D-Bus interface without cloning the full Notifications struct.

Tests: 16/16 pass. New tests:
  - expiry_removes_record_and_emits_closed_reason
  - replaces_id_reuses_existing_record_and_logs_replacement
  - vanished_sender_records_are_purged
  - notifications_are_bounded_at_1024_entries
  - invoke_action_removes_record
This commit is contained in:
2026-07-28 07:31:56 +09:00
parent cd429e8c74
commit 4d00f7ad09
File diff suppressed because it is too large Load Diff