Volver a la wiki

Motor de Integridad F1 — cruce plano↔realidad con métricas separadas

Descripción General

Motor de Integridad F1 es la primera fase de la Barra de Integridad (Integrity Bar): un sistema que computa automáticamente la clasificación de cada equipo documentado en los racks contra la realidad observada en la LAN del cliente, a través de dos señales independientes:

  1. DeviceProfile del discovery (qué ve el sensor de red)
  2. MonitoringTarget del monitoreo (qué dice el agente local)

El motor NUNCA usa solo una métrica de confianza. En su lugar, separa deliberadamente:

Un equipo no observable (sin IP de gestión, sin perfil, sin monitorización) jamás baja la fidelidad: se va a cuarentena (unobservable).

Cronología y Contexto

Clasificación por Equipo

Cada dispositivo del plano se clasifica en uno de estos montones:

ClaseCriterioAcción
matched_aliveIdentidad conocida + evidencia fresca de vida (perfil ≤7d O monitorización up)OK
matched_staleIdentidad conocida pero sin evidencia fresca (drift candidato: retirado/apagado/desconectado)Revisar
ip_conflictLa identidad no cuadra: perfil en otra IP O IP del plano vinculada a otro equipoRevisar
unobservablePlano sin identidad de red (sin IP, sin perfil, sin monitorización)Cuarentena (NO baja fidelity)

A nivel organización, se añade:

ClaseCriterioAcción
undocumentedPerfil vivo en la LAN que no está en ningún plano (drift candidato: equipo montado sin documentar)Revisar

Componentes del Motor

Servicio racks.services.integrity

Función pública: compute_integrity(organization, fresh_days=7, target_fresh_minutes=30)

Función de persistencia: persist_snapshots(organization, result, snapshot_date=None)

Modelo RackIntegritySnapshot

class RackIntegritySnapshot(models.Model):
    organization = ForeignKey(Organization, CASCADE)
    rack = ForeignKey(Rack, CASCADE, null=True, blank=True)  # NULL = org-summary
    date = DateField()
    
    fidelity_pct = FloatField(null=True, blank=True)
    coverage_pct = FloatField(null=True, blank=True)
    
    counts = JSONField(default=dict)  # {matched_alive, matched_stale, ...}
    findings = JSONField(default=list)  # [{device_id, classification, reason, evidence}, ...]
    
    # RLS: patrón core-0029, migraciones 0014+0015

Comando rack_integrity_probe

python manage.py rack_integrity_probe --org 1
python manage.py rack_integrity_probe --org 1 --save        # guarda snapshot
python manage.py rack_integrity_probe --org 1 --json        # JSON para logs

Salida human-readable con tabla de racks + avisos a revisar (stale/conflict) + vivos sin documentar.

Task Diaria compute_integrity_snapshots

@db_periodic_task(crontab(hour="4", minute="15"))  # 04:15 UTC, tras backups
@lock_task("rack-integrity-snapshots")
def compute_integrity_snapshots():
    # Itera org.all() con bypass GUC ('0')
    # Persiste snapshot para cada org
    # Logger: info al terminar

Decisiones de Diseño Clave

1. Dos Métricas Separadas (NO Una Única Confianza)

Problema: Un equipo sin perfil de discovery pero con monitorización “up” parecía 100% confiable. Pero el sensor tal vez nunca lo vió → fidelidad falsa.

Solución RESHAPE (roast 17-07):

Un equipo unobservable contribuye al denominador de coverage, pero NUNCA al numerador de fidelity.

2. Sin Impacto de Usuario (Gate Antes de Exponer)

F1 es puro motor + datos. La UI llega en F2-F4 tras validar que el detector acierta ≥80% (task #202, design partner en sept).

Si exponemos ahora, confundimos al usuario con un beta que no sabemos si funciona → pérdida de confianza.

3. RLS Patrón core-0029

Migraciones 0014 (tabla) + 0015 (política). El scoping manual en Huey (GUC ‘0’ con bypass admin) es idéntico al del resto de tasks de background.

4. Umbrales Ajustables, Pré-Declarados

Los defaults (DEFAULT_FRESH_DAYS=7, DEFAULT_TARGET_FRESH_MINUTES=30) son ajustables por flag de comando. El gate de calibración decide los definitivos, no hardcodeamos.

Límites Honestos

Véase también

Subir