Ir al contenido

Alarms y notificaciones

CORE-M modela las alarms como entidades de primera clase, acotadas al tenant — no como simples líneas de log. Una alarm tiene un ciclo de vida, un propietario, un historial de comentarios y cambios de estado, e impulsa notificaciones. Esta página cubre la entity alarm y su ciclo de vida, y luego el sistema de notificaciones que enruta las alarms hacia personas y sistemas.

Las alarms son creadas por rule chains, cambios de estado de dispositivos, adaptadores de protocolo o monitores del sistema, y se almacenan en el namespace de Aerospike rules, set alarms, con clave {tenant_id}:{alarm_id}.

Un registro de alarm contiene alarm_id, tenant_id, originator_type, originator_id, type, severity, status, assignee_user_id, start_ts, end_ts, acknowledged_at, cleared_at, details_json, latest_event_hash, created_at, updated_at y version. Los índices secundarios permiten filtrar por tenant_id, originator_id, severity, status, type y las marcas de tiempo — lo que alimenta los filtros del centro de alarms.

Cuando se dispara un nodo create-alarm (por ejemplo, el dispositivo D1 reporta una temperatura de 95), se crea una alarm con status="active_unack", la severity configurada, originator_id="D1" y el type definido por la regla. La plataforma entonces:

  • Publica alarm.created.T1.{alarm_id}.
  • Envía alarm_update a los clientes WebSocket suscritos a las alarms del tenant.
  • Actualiza corem_alarms_total{severity,status}.

Una alarm tiene dos dimensiones ortogonales: active vs cleared (la condición) y unacknowledged vs acknowledged (atención del operador). Los cuatro valores de status las combinan:

StatusCondiciónOperador
active_unackAún activaSin acknowledge todavía
active_ackAún activaCon acknowledge
cleared_unackClearedSin acknowledge todavía
cleared_ackClearedCon acknowledge

Conceptualmente, este es el flujo familiar triggered → acknowledged → cleared, con el matiz de que una alarm cleared puede reabrirse si la condición vuelve a ocurrir.

stateDiagram-v2
  [*] --> active_unack: alarm created
  active_unack --> active_ack: acknowledge
  active_unack --> cleared_unack: clear
  active_ack --> cleared_ack: clear
  cleared_unack --> cleared_ack: acknowledge
  cleared_ack --> active_unack: condition recurs (reopen)
  cleared_unack --> active_unack: condition recurs (reopen)
  1. Acknowledge. Un usuario con el permiso alarms hace acknowledge de la alarm, opcionalmente con un comentario (“Investigando”). El status pasa a active_ack; se establecen acknowledged_by y acknowledged_at; el comentario se añade al historial; se escribe el audit event alarm.acknowledged.

  2. Clear. Hacer clear con una resolución (“Ventilador reemplazado”) mueve el status a cleared_ack, registra cleared_by, cleared_at y resolution, y publica alarm.cleared.T1.{alarm_id}.

  3. Reopen. Si la misma condición de alarm se vuelve a disparar dentro de la ventana de deduplication, la alarm se reabre a active_unack, reopened_count se incrementa, y los metadatos del clear anterior permanecen en el historial.

Las alarms se deduplican por originator y type. Si ya hay una alarm activa para (tenant, device, type), un nuevo disparo actualiza la alarm existente en lugar de generar una nueva: latest_event_hash y updated_at cambian, repeat_count se incrementa, y se publica alarm.updated.T1.{alarm_id}. Esto evita que un sensor ruidoso inunde el centro de alarms con duplicados.

La severity escala con cada repetición. Si una alarm warning activa recibe un nuevo disparo con severity critical, la severity de la alarm pasa a critical, escalation_count se incrementa, y las reglas de notificación de alarms críticas se reevalúan.

Cada transición del ciclo de vida — acknowledge, clear, reopen, comentario, asignación — queda registrada en el historial de la alarm, y las transiciones notables emiten audit events. Esto da a los operadores un registro completo, de solo adición (append-only), de quién hizo qué y cuándo.

Las transiciones del ciclo de vida usan comprobaciones de generation de Aerospike (compare-and-swap) para evitar actualizaciones perdidas. Si dos operadores cargan la alarm A1 en la versión 4 y ambos intentan hacer acknowledge:

  • La primera escritura tiene éxito.
  • La segunda, que aún sostiene la versión obsoleta 4, se rechaza con ABORTED / HTTP 409, y la respuesta devuelve la versión y el status actuales para que la UI pueda refrescarse.

El mismo CAS por registro aplica a las acciones masivas: hacer acknowledge de 25 alarms a la vez aplica CAS por cada alarm e informa los fallos por alarm_id, de modo que la UI puede mostrar éxito parcial cuando algunas alarms cambiaron de forma concurrente.

Las alarms impulsan notificaciones: mensajes entregados a personas o sistemas. Los notification targets, las plantillas y las reglas están acotados al tenant y se almacenan en el namespace de Aerospike rules, set notifications, con clave {tenant_id}:{notification_id}.

Tipo de targetNotas
emailEntrega por SMTP
webhookHTTP POST genérico
webhook compatible con SlackEstilo incoming-webhook
proveedor de SMSA través del gateway de SMS configurado
notificación in-appMostrada dentro de CORE-M
evento RedpandaPublicado para consumidores downstream

Las plantillas renderizan el cuerpo del mensaje con sustitución de variables a partir de campos de la alarm y del dispositivo. Las plantillas están versionadas: editar una plantilla versiona el contenido anterior y escribe un audit event con template_id, old_version, new_version y actor_user_id.

Una regla de notificación lo une todo: un matcher (p. ej. severity="critical" y status="active_unack"), uno o más targets, una plantilla, además de quiet hours, escalation, suppression y una ventana de deduplication. Cuando una alarm coincide con una regla, se crea un registro de notificación con status="scheduled", se publica notification.scheduled.T1.{notification_id}, y el worker renderiza la plantilla.

flowchart TD
  match([Alarm matches rule]) --> sched["status = scheduled"]
  sched --> send{Deliver to target}
  send -->|accepted| delivered["status = delivered<br/>record delivered_at,<br/>provider_message_id"]
  send -->|error| retry{Retries left?}
  retry -->|yes| backoff["Exponential backoff,<br/>retry per rule policy"]
  backoff --> send
  retry -->|no| failed["status = failed<br/>store failure_reason"]

En caso de éxito, la notificación pasa a delivered, registrando delivered_at y el provider_message_id, y publicando notification.delivered.T1.{notification_id}. En caso de fallo, el worker reintenta con exponential backoff según la política de la regla; tras el máximo de intentos, el status pasa a failed, failure_reason almacena el error final del proveedor, y corem_notification_delivery_failures_total{target_type} se incrementa.

Las reglas de notificación admiten varios controles para mantener las alertas útiles en lugar de ruidosas:

  • Quiet hours. Aplazan las notificaciones no críticas durante una ventana (p. ej. 22:00–07:00 en la hora local del tenant). Una alarm warning a las 23:00 produce una notificación deferred que se retiene hasta las 07:00, sin ninguna llamada al proveedor mientras tanto.
  • Bypass de críticas. Una regla puede permitir que las alarms critical omitan las quiet hours — una alarm crítica durante la ventana silenciosa se programa de inmediato.
  • Escalation. Si una alarm permanece en active_unack más allá de un umbral (p. ej. 15 minutos), el planificador de escalation programa la notificación del siguiente nivel (p. ej. nivel 2 → “oncall-manager”) y registra escalation_level en el historial de la alarm.
  • Ventanas de suppression y dedup. Las ventanas de suppression y deduplication por target evitan que disparos repetidos generen notificaciones repetidas.

Donde la política del tenant lo permite, los usuarios pueden establecer preferencias personales de notificación — por ejemplo, optar por no recibir email mientras mantienen las notificaciones in-app — y el cambio se audita (notification.preference.updated). Cada cambio de regla de notificación y cada cambio de estado de entrega produce un audit event, de modo que toda la configuración de alertas y sus resultados de entrega son trazables.