Volver a la wiki

Cola de auditoría C (task #286): ITSM, SLA, notificaciones y tareas de fondo — marcar y notificar en una sola transacción, ventanas de mantenimiento con fail-open, secretos en el log

Cuándo

04-09-2026 · commit 63b62635 (PR #498, v1.106.0). Tercera entrega de la “cola de auditoría” del task #286 sobre el dominio monitoring — la primera (v1.104.0, “cola auditoría A”) cubrió sondas y targets, la segunda (v1.105.0, “cola auditoría B”) cerró CNS e insights de IA; esta cierra ITSM, SLA, notificaciones y tareas de fondo con 13 hallazgos MEDIA propios más 14 adicionales de una revisión en Opus sobre el mismo PR. 41 tests nuevos en tests/api/test_audit_monitoring_c.py.

Síntomas visibles

  1. check_sla_breaches() marcaba TODOS los breaches y enviaba TODOS los webhooks dentro de la misma transacción: un envío lento la mantenía abierta minutos, y cualquier fallo (incluso dentro de NotificationLog.objects.create) revertía todos los flags sla_*_breached ya puestos esa pasada mientras los webhooks que ya habían disparado no se podían “des-enviar” — los mismos insights se re-notificaban cada minuto.
  2. check_escalations() tenía el mismo patrón (marcar + notificar en una transacción) más una N+1: policy.levels.filter(...) descartaba el prefetch_related y relanzaba una query por insight × política cada 2 minutos.
  3. Un AlertEvent nunca se auto-resolvía al cesar la condición — solo un clic humano en “Resolver” ponía resolved_at, así que una alerta transitoria quedaba “activa” en el panel para siempre; el evento agrupado (“N dispositivos afectados”) tampoco se cerraba al caer por debajo del umbral de agrupación, contando el mismo incidente dos veces en una recuperación parcial.
  4. Una ventana de mantenimiento solo suprimía notificaciones si estaba vinculada explícitamente al target — una ventana org-wide (sin targets) suprimía insights pero no el canal de notificaciones.
  5. error_message de una notificación fallida guardaba el texto crudo de la excepción; para Slack/Teams ese texto incluye la URL completa del webhook (el secreto), servida en claro por /notifications/log a cualquier usuario con itsm:view.
  6. El chequeo de ventana de mantenimiento en dispatch_notification envolvía la comprobación en un except: pass — si fallaba, la notificación se enviaba igualmente, abriendo una vía para saltarse la supresión.
  7. Un channel_type desconocido en NotificationChannel.config (fila corrupta o vieja) no llamaba a ningún transporte pero tampoco marcaba fallo: la notificación se perdía sin rastro.
  8. Ventanas de mantenimiento sin exigir zona horaria ni end_at > start_at.
  9. NotificationLog crecía sin purga — y cada chequeo de rate-limit (_tenant_rate_limited) hace COUNT sobre esa tabla.
  10. Las tareas periódicas de Huey de este fichero (SLA, escalado, patrones, expiración, purga, reconciliación) leen/escriben tablas con RLS sin pasar por el middleware que fija app.current_org_id — dependían de un default de rol implícito y no declarado.
  11. detect_recurring_patterns corría todas las organizaciones dentro de una sola transacción: una excepción en la org 3 dejaba la transacción “necesita rollback” y la query de la org 4 reventaba con TransactionManagementError, matando la pasada entera en vez de saltarse solo la org fallida.
  12. Acknowledge-all sin tope de tamaño ni aislamiento por insight.

Causa raíz

El mismo patrón de las dos entregas anteriores de esta cola (A y B): operación de base de datos y llamada de red externa comparten una transacción larga, así que un fallo de red revierte estado que ya era correcto y un éxito de red no se puede deshacer si algo posterior falla. A eso se suma guardas de seguridad con except: pass (fail-open en vez de fail-closed) y tablas de auditoría/rastro sin política de retención desde el día uno.

Fix aplicado

Commit 63b626352ba4a1971d121299719507e341a5f3b4 (PR #498):

Lecciones

Separar “marcar” (DB) de “notificar” (I/O externo) en transacciones distintas es el patrón que se repite en las tres entregas de esta cola (A, B, C): meter una llamada de red dentro de una transacción larga convierte cualquier fallo de red en una reversión de estado que ya era correcto, y cualquier éxito de red en algo que no se puede deshacer si algo posterior falla. Y un except: pass alrededor de una guarda de seguridad es indistinguible de “todo va bien” para quien lee el log — tiene que fallar cerrado y dejar rastro.

Preventivos futuros

Véase también

Subir