- notifyListChange(listId, actorId, windowMs?) wraps coalesceListPush with a
dispatch closure that resolves actor name + list name from DB, builds
audience as owner ∪ list_shares MINUS actorId (D-03), and calls
dispatchPush per accessible subscriber subscription
- D-02 generic copy: '{Actor} made {N} changes to {ListName}' — no item text
- D-03 self-suppression: actorId filtered from audience before subscription load
- T-05-14: audience strictly scoped to list access (owner + list_shares only)
- T-05-15: no item text in notification body
- Empty audience and missing subscriptions are silent no-ops
- Tests: 5/5 GREEN (burst→1 push, self-suppress, access scope, empty audience)