SIENTIAPDE-2149: model_ids allow-list filter for receiver groups
New, separate model_id-based filter distinct from the existing ignore_models_id deny-list (SIENTIAPDE-2012): a group configuring model_ids only receives notifications from those models. Both axes apply together (AND) when a group sets both. Design decisions made while implementing (matching the levels/ ignore_models_id split from SIENTIAPDE-2011/2012, not yet confirmed with the team beyond what's in the ticket): - Query-time filter: union of every active group's model_ids, applied only when ALL active groups set one -- mirrors levels' exact union-or-nothing logic in _compute_query_time_filter. Combines cleanly with ignore_models_id's intersection $nin on the same model_id field. - Per-group filter: extended _check_levels_and_ignore_models_id (now _check_levels_and_model_filters) in slot_manager.py, and the inline condition in formatters.py's filter_notification_reports. - Empty/missing model_ids means no restriction, same convention as levels and ignore_models_id. Covered by new unit tests (slot_manager, formatters, _compute_query_time_filter) and two new e2e tests against real Mongo/Redis/Temporal: per-group allow-list interacting with another unrestricted group, and the query-time union excluding a model outside every active group's allow-list. Not yet verified on the real cluster -- blocked on the dev cluster's package index not auto-syncing new sientia_do releases (separate issue, being investigated for SIENTIAPDE-2013). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -826,6 +826,7 @@ class Formatters(SientiaMonitoring):
|
||||
- ignore (list, optional): Notification IDs to exclude
|
||||
- levels (list, optional): Notification levels to include
|
||||
- ignore_models_id (list, optional): Model IDs to exclude
|
||||
- model_ids (list, optional): Model IDs to allow (SIENTIAPDE-2149)
|
||||
|
||||
Returns:
|
||||
dict[str, Any]: Filtered receiver groups with their notifications, keyed by group_name.
|
||||
@@ -851,6 +852,7 @@ class Formatters(SientiaMonitoring):
|
||||
ignore_list = receiver_group.get('ignore', [])
|
||||
levels_list = receiver_group.get('levels')
|
||||
ignore_models_id_list = receiver_group.get('ignore_models_id') or []
|
||||
model_ids_list = receiver_group.get('model_ids')
|
||||
|
||||
for notification in notification_package:
|
||||
alert_type = 'reports'
|
||||
@@ -868,6 +870,7 @@ class Formatters(SientiaMonitoring):
|
||||
not ignore_models_id_list
|
||||
or notification.get('model_id') not in ignore_models_id_list
|
||||
)
|
||||
and (not model_ids_list or notification.get('model_id') in model_ids_list)
|
||||
):
|
||||
receiver_groups[group_name]['notifications'].append(notification)
|
||||
already_added_keys.append(key)
|
||||
|
||||
@@ -398,6 +398,7 @@ class SlotManager(SientiaMonitoring):
|
||||
- ignore (list, optional): Notification IDs to exclude
|
||||
- levels (list, optional): Notification levels to include
|
||||
- ignore_models_id (list, optional): Model IDs to exclude
|
||||
- model_ids (list, optional): Model IDs to allow (SIENTIAPDE-2149)
|
||||
- notification_ttl (int): Seconds before considering notification persistent
|
||||
|
||||
Returns:
|
||||
@@ -450,6 +451,7 @@ class SlotManager(SientiaMonitoring):
|
||||
ignore_list = receiver_group.get('ignore', [])
|
||||
levels_list = receiver_group.get('levels')
|
||||
ignore_models_id_list = receiver_group.get('ignore_models_id') or []
|
||||
model_ids_list = receiver_group.get('model_ids')
|
||||
|
||||
notifications = []
|
||||
for notification in notification_package:
|
||||
@@ -462,8 +464,8 @@ class SlotManager(SientiaMonitoring):
|
||||
and self._check_keys_and_notif_id(
|
||||
notification_id, ignore_list, key, already_added_keys
|
||||
)
|
||||
and self._check_levels_and_ignore_models_id(
|
||||
notification, levels_list, ignore_models_id_list
|
||||
and self._check_levels_and_model_filters(
|
||||
notification, levels_list, ignore_models_id_list, model_ids_list
|
||||
)
|
||||
):
|
||||
notifications.append(notification)
|
||||
@@ -493,23 +495,29 @@ class SlotManager(SientiaMonitoring):
|
||||
return 'do_nothing'
|
||||
|
||||
@staticmethod
|
||||
def _check_levels_and_ignore_models_id(notification, levels_list, ignore_models_id_list):
|
||||
def _check_levels_and_model_filters(
|
||||
notification, levels_list, ignore_models_id_list, model_ids_list
|
||||
):
|
||||
"""
|
||||
Check if the notification's level is in the levels_list and its model_id is not in the ignore_models_id_list.
|
||||
Check the notification's level and model_id against a group's filters.
|
||||
|
||||
Args:
|
||||
notification (dict): The notification to check.
|
||||
levels_list (list, optional): List of levels to include. If None, all levels are included.
|
||||
ignore_models_id_list (list, optional): List of model_ids to exclude. If None, no models are excluded.
|
||||
model_ids_list (list, optional): List of model_ids to allow (SIENTIAPDE-2149).
|
||||
If None/empty, all models are allowed. Independent of ignore_models_id_list --
|
||||
both apply together when both are configured.
|
||||
|
||||
Returns:
|
||||
bool: True if the notification passes the checks, False otherwise.
|
||||
"""
|
||||
level_check = not levels_list or notification.get('level') in levels_list
|
||||
model_id_check = (
|
||||
ignore_models_id_check = (
|
||||
not ignore_models_id_list or notification.get('model_id') not in ignore_models_id_list
|
||||
)
|
||||
return level_check and model_id_check
|
||||
model_ids_check = not model_ids_list or notification.get('model_id') in model_ids_list
|
||||
return level_check and ignore_models_id_check and model_ids_check
|
||||
|
||||
@staticmethod
|
||||
def _check_keys_and_notif_id(
|
||||
|
||||
@@ -137,6 +137,12 @@ class LoadNotificationPackage:
|
||||
is only safe to drop from the fetch if EVERY active group ignores it.
|
||||
A group with no `ignore_models_id` is treated as "ignores nothing" (empty
|
||||
set), which naturally zeroes out the intersection via set math.
|
||||
- model_ids (SIENTIAPDE-2149): union of every group's `model_ids` allow-list,
|
||||
mirroring `levels`' own union-or-nothing logic. If ANY active group has no
|
||||
restriction (missing/empty `model_ids`), that group wants every model, so
|
||||
the query can't restrict to an allow-list at all. `model_ids` and
|
||||
`ignore_models_id` are independent axes and can both narrow the same
|
||||
`model_id` field at once (Mongo supports multiple operators on one field).
|
||||
"""
|
||||
if not sending_configs:
|
||||
return default_filter
|
||||
@@ -160,7 +166,21 @@ class LoadNotificationPackage:
|
||||
]
|
||||
models_intersection = set.intersection(*sending_configs_ignore_models_id)
|
||||
|
||||
sending_configs_model_ids = [group.get('model_ids') for group in sending_configs]
|
||||
|
||||
model_id_filter: dict[str, Any] = {}
|
||||
|
||||
if all(sending_configs_model_ids):
|
||||
model_ids_union: set[str] = set()
|
||||
for model_ids in sending_configs_model_ids:
|
||||
if model_ids:
|
||||
model_ids_union.update(model_ids)
|
||||
model_id_filter['$in'] = sorted(model_ids_union)
|
||||
|
||||
if models_intersection:
|
||||
query_filter['model_id'] = {'$nin': sorted(models_intersection)}
|
||||
model_id_filter['$nin'] = sorted(models_intersection)
|
||||
|
||||
if model_id_filter:
|
||||
query_filter['model_id'] = model_id_filter
|
||||
|
||||
return query_filter
|
||||
|
||||
Reference in New Issue
Block a user