CreaRack-SL

Modelo: IntegrityAck (veredictos de la cola)

Modelo: IntegrityAck — Veredictos de reconciliación con vinculación a incidencias

Archivo: racks/models.py (línea ~339)
Módulo: racks.models
Tabla: racks_integrityack
Cambio: Nuevo FK insight (migración racks 0018, v1.61.0)

Propósito

Registra los veredictos humanos emitidos en la cola de reconciliación de la Barra de Integridad:

  • “Plan updated” — usuario corrigió el plano.
  • “Real change” — usuario confirma que la red cambió (abre incidencia ITSM).
  • “False alarm” — sensor erró (silencia 30 días).

Nuevo en F3: FK opcional a AIInsight.id para vincular veredictos “Real change” a sus incidencias correspondientes en CNS/ITSM.

Schema

class IntegrityAck(models.Model):
    # Existentes (F1-F2)
    organization = ForeignKey(Organization, on_delete=models.CASCADE)
    device = ForeignKey(Device, on_delete=models.CASCADE, null=True, blank=True)
    profile = ForeignKey(DeviceProfile, on_delete=models.CASCADE, null=True, blank=True)
    created_at = DateTimeField(auto_now_add=True)
    created_by = ForeignKey(User, on_delete=models.SET_NULL, null=True, blank=True)
    kind = CharField(
        max_length=20,
        choices=[
            ("matched_stale", "Equipo documentado pero sin vida reciente"),
            ("undocumented", "Equipo vivo pero no documentado"),
            ("identity_conflict", "Conflicto de identidad"),
        ],
    )
    verdict = CharField(
        max_length=20,
        choices=[
            ("plan_updated", "Plan updated"),
            ("real_change", "Real change"),
            ("false_alarm", "False alarm"),
        ],
    )
    note = TextField(blank=True)
    subject_label = CharField(max_length=200, blank=True)
    muted_until = DateTimeField()

    # Nuevo en F3 (migración 0018)
    insight = ForeignKey(
        "monitoring.AIInsight",
        on_delete=models.SET_NULL,
        null=True,
        blank=True,
        related_name="integrity_acks",
    )

Campos

Existentes (F1-F2)

CampoTipoDescripción
organizationFK(Organization)Org propietaria (RLS).
deviceFK(Device)Equipo documentado (si kind=“matched_stale” o “identity_conflict”).
profileFK(DeviceProfile)Perfil de red (si kind=“undocumented”).
created_atDateTimeFieldTimestamp del veredicto.
created_byFK(User)Usuario que emitió el veredicto.
kindCharFieldTipo de hallazgo: matched_stale, undocumented, identity_conflict.
verdictCharFieldplan_updated, real_change, false_alarm.
noteTextFieldAnotación opcional del usuario.
subject_labelCharFieldIdentificador legible congelado en el click (p.ej. “Router A @ Rack 5 U42”).
muted_untilDateTimeFieldHasta cuándo se silencia el aviso:
- plan_updated: +30 días
- real_change: +30 días
- false_alarm: +30 días

Nuevo en F3 (Migración 0018)

CampoTipoDescripción
insightFK(AIInsight)Incidencia CNS/ITSM abierta por veredicto “real_change” (nullable).

Características:

  • nullable & blank: No todos los acks tiene incidencia (p.ej. sujetos sin target de monitorización).
  • on_delete=SET_NULL: Si el ticket caduca en Observatory, el ack preserva su historial.
  • related_name=“integrity_acks”: Desde AIInsight: insight.integrity_acks.all() lista todos los acks vinculados.

Flujo de vinculación (F3)

1. Usuario emite veredicto “Real change” en /integrity/

// static/js/pages/integrity.js
async function sendVerdict(btn) {
    const body = {
        kind: btn.dataset.kind,
        verdict: btn.dataset.verdict,
        device_id: btn.dataset.deviceId || null,
        profile_id: btn.dataset.profileId || null,
    };
    const res = await fetch("/api/racks/integrity/ack", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify(body),
    });
    if (!res.ok) throw new Error(`HTTP ${res.status}`);
    const out = await res.json();
    showItsmNote(out.itsm);  // ← Muestra respuesta ITSM
    await load();  // Repinta con veredicto aplicado
}

2. Endpoint /api/racks/integrity/ack (POST)

# racks/api/integrity.py
def ack_finding(request, data: AckIn):
    # ... (crea IntegrityAck sin incidencia aún)
    ack = IntegrityAck.objects.create(
        organization=org,
        device=device,
        profile=profile,
        kind=data.kind,
        verdict=data.verdict,
        subject_label=...,
        muted_until=...,
        created_by=request.user,
    )

    # F3: un "Real change" abre incidencia ITSM en background
    itsm: dict | None = None
    if data.verdict == integrity.VERDICT_REAL_CHANGE:
        from racks.services.integrity_itsm import resolve_ticket_target
        from racks.tasks import open_integrity_drift_ticket

        if resolve_ticket_target(org, device=device, profile=profile):
            open_integrity_drift_ticket(ack.id)  # Encola en Huey
            itsm = {"queued": True}
        else:
            itsm = {"queued": False, "reason": "subject has no monitoring target"}

    return 200, {
        "ok": True,
        "ack_id": ack.id,
        "muted_until": ack.muted_until.isoformat(),
        "gate": integrity.gate_stats(org),
        "itsm": itsm,
    }

3. Tarea Huey open_integrity_drift_ticket(ack_id)

# racks/tasks.py
@db_task()
def open_integrity_drift_ticket(ack_id):
    from racks.services import integrity_itsm
    try:
        insight_id = integrity_itsm.open_drift_ticket(ack_id)
        if insight_id:
            logger.info(f"[integrity] drift ticket {insight_id} for ack {ack_id}")
    except Exception:
        logger.exception(f"[integrity] drift ticket failed for ack {ack_id}")

4. Servicio open_drift_ticket() vincula el ack

# racks/services/integrity_itsm.py
insight = async_to_sync(create_drift_insight)(
    target=target,
    anomaly_description=description,
    evidence=evidence or {},
    subject_label=label,
    provider_name=get_default_provider_name(),
)
ack.insight = insight  # ← Vinculación
ack.save(update_fields=["insight"])
logger.info(f"[integrity] drift ticket {insight.id} opened for ack {ack.id}")

Reconciliación (F3 Hook)

Cuando el usuario reconoce (acknowledge) la incidencia en Observatory:

# monitoring/services/insight_service.py
def acknowledge_insight(insight: AIInsight, user=None, notes: str = ""):
    # ... (estándar: marca insight como ACKNOWLEDGED)
    
    # F3: des-silenciar acks vinculados para re-verificar el sujeto
    now = timezone.now()
    unmuted = IntegrityAck.objects.filter(
        insight=insight,
        muted_until__gt=now,
    ).update(muted_until=now)
    if unmuted:
        logger.info(f"Insight {insight.id} acknowledged — integrity re-check armed ({unmuted} ack unmuted)")

Efecto: El próximo probe de integridad re-verifica el sujeto sin esperar a la caducidad original (típicamente 30 días).

Impacto

API Response F3

{
  "ok": true,
  "ack_id": 123,
  "muted_until": "2026-08-17T17:40:28Z",
  "gate": {...},
  "itsm": {
    "queued": true
  }
}

o

{
  "ok": true,
  "ack_id": 124,
  "muted_until": "2026-08-17T17:40:28Z",
  "gate": {...},
  "itsm": {
    "queued": false,
    "reason": "subject has no monitoring target"
  }
}

o (otros veredictos)

{
  "ok": true,
  "ack_id": 125,
  "muted_until": "2026-08-17T17:40:28Z",
  "gate": {...},
  "itsm": null
}

Queries relacionadas

# Todos los acks de un usuario en una org
user_acks = IntegrityAck.objects.filter(created_by=user, organization=org)

# Acks sin incidencia (sujetos sin monitorización)
orphan_acks = IntegrityAck.objects.filter(insight__isnull=True, verdict="real_change")

# Acks aún silenciados
muted_acks = IntegrityAck.objects.filter(
    muted_until__gt=timezone.now()
)

# Incidencia vinculada a un ack
if ack.insight:
    print(f"Incidencia: {ack.insight.summary}")
else:
    print("Sin incidencia")

Testing

Cubierto por 5 de 8 tests:

  • ✅ Creación de ack con veredicto “real_change”.
  • ✅ FK insight se rellena en background.
  • ✅ Acks sin target → sin insight (FK nulo).
  • ✅ Acknowledge dessilencia acks vinculados.
  • ✅ Idempotencia (no se duplican acks).

Véase también

  • [[feature—racks—barra-integridad-f3]] — feature global y flujo completo.
  • [[entity—racks—service—integrity-itsm]] — servicio que rellena el FK insight.
  • [[entity—monitoring—model—aiinsight]] — incidencias que se vinculan.
  • [[entity—racks—model—device]] — equipo potencial sujeto del ack.