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)
| Campo | Tipo | Descripción |
|---|---|---|
organization | FK(Organization) | Org propietaria (RLS). |
device | FK(Device) | Equipo documentado (si kind=“matched_stale” o “identity_conflict”). |
profile | FK(DeviceProfile) | Perfil de red (si kind=“undocumented”). |
created_at | DateTimeField | Timestamp del veredicto. |
created_by | FK(User) | Usuario que emitió el veredicto. |
kind | CharField | Tipo de hallazgo: matched_stale, undocumented, identity_conflict. |
verdict | CharField | plan_updated, real_change, false_alarm. |
note | TextField | Anotación opcional del usuario. |
subject_label | CharField | Identificador legible congelado en el click (p.ej. “Router A @ Rack 5 U42”). |
muted_until | DateTimeField | Hasta 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)
| Campo | Tipo | Descripción |
|---|---|---|
insight | FK(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
insightse 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.