Descripción general
La Barra de Integridad F2 cierra la visión interna del motor de validación plano↔realidad (iniciado en Motor de Integridad F1, v1.59.0). Permite ver el estado completo del radar de cada organización y gestionar la cola de reconciliación mediante veredictos humanos.
Ubicación (desde v1.63.0, 2026-07-23): pestaña Integrity de Observatory (Overview · CNS · ITSM · Integrity). Hasta v1.62.x fue página propia (/integrity/) con botón en el header; esa URL se conserva como redirect a /monitoring/?tab=integrity (bookmarks). Razón del traslado (decisión de producto de Edu): el bucle drift → filtro CNS → ticket ITSM → reconciliar vive en superficies vecinas y la audiencia es la misma.
Publicación: v1.60.0 (2026-07-17)
Estado: Nace completamente apagado — no visible para usuario alguno hasta que se active el módulo integrity en extra_modules de una org específica (beta org 1, activación manual post-deploy).
Por qué “nace apagado”
La v1.59.0 introdujo el motor que detecta divergencias plano↔realidad:
- Plan dice “Device A está en U10”, pero la red no lo ve (stale).
- La red ve “IP 10.0.0.5 es Cisco”, pero el plan no lo documenta (undocumented).
- Dos devices afirman ser “IP 10.0.0.1” (IP conflict).
Pero la calidad del motor depende de su precisión, que a su vez depende del ambiente de testing. La v1.60.0 añade los veredictos humanos (IntegrityAck) que calibran automáticamente la métrica del gate (#202):
- Click
plan_updatedoreal_change→ el aviso era SEÑAL (cambio real) - Click
false_alarm→ el aviso era RUIDO (error del sensor)
Hasta que el ratio señal/ruido sea digno de confianza, no se muestra a clientes. Por eso:
# core/migrations/0030_integrity_module.py
# El módulo existe pero NO se añade a ningún Plan ni org
SaaSModule.objects.get_or_create(
slug="integrity",
defaults={...}
)
Solo con org.extra_modules.add(integrity_module) → aparece la pestaña Integrity en Observatory + API.
Arquitectura de componentes
1. Módulo SaaS integrity (core)
- Registrado en DB (migración
0030). - URL prefixes gateadas en
module_registry:/integrity/,/api/racks/integrity. - Sin
Plan, sinOrganization.plans: siempreextra_modules(opt-in). - La visibilidad de la pestaña en Observatory la gatea el template (
{% ifmodule "integrity" %}enobservatory.html): una org sin el módulo ni la pinta.
2. Modelo IntegrityAck (racks)
- Tabla: Veredictos de la cola. Una fila per aviso → veredicto usuario.
- device XOR profile: Avisos de rack apuntan a
Device; avisos “undocumented” (vivos sin plano) apuntan aDeviceProfile. - Veredictos:
plan_updated(3 días silencio) /real_change(30 días) /false_alarm(30 días + cuarentena muted). - RLS: Tenant isolation (org_id, bypass admin).
3. Servicio Motor (racks/services/integrity.py)
Funciones públicas:
compute_integrity(org, fresh_days=7)— retorna estado completo: fidelity %, coverage %, hallazgos por rack + lista undocumented. Ahora integraba veredictos vigentes para:false_alarm→ saca el device de fidelity (cuarentena “muted”).plan_updated/real_change→ marcan hallazgo ack-ado pero lo contabilizan como señal.
gate_stats(org)— métrica del gate #202:{signal, noise, total, signal_pct}.
Sub-componentes:
_load_active_acks(org, now)— índice de veredictos vigentes (no mutados aún) por sujeto._ack_info(ack)— serializa veredicto para respuesta JSON.- Constantes:
ACK_KINDS(matched_stale, ip_conflict, undocumented) /VERDICTS(plan_updated, real_change, false_alarm) /ACK_MUTE_DAYS{3, 30, 30}.
4. API (racks/api/integrity.py)
Dos endpoints públicos:
GET /api/racks/integrity/overview (usuario miembro org)
{
"result": { "summary": {...}, "racks": [{...}], "undocumented": [...] },
"history": [{"date": "2026-07-10", "fidelity_pct": 98.5, ...}, ...],
"gate": { "signal": 12, "noise": 2, "signal_pct": 85.7, ... }
}
POST /api/racks/integrity/ack (usuario racks:edit)
{
"kind": "matched_stale",
"verdict": "plan_updated",
"device_id": 42,
"note": "El switch ya está retirado del rack"
}
→ 200 { "ok": true, "ack_id": 1234, "muted_until": "2026-07-20T...", "gate": {...} }
Gateado por módulo — 403 sin integrity activo.
5. Vistas (desde v1.63.0)
racks/views.py::integrity_view(/integrity/): solo redirect a/monitoring/?tab=integrity. El gating por módulo (middleware) aplica ANTES del redirect: org sin módulo → 403.monitoring/views.py::observatory_view(/monitoring/): renderizaobservatory.html, que incluye el markup de la pestaña Integrity (module-gated) y pasaintegrity_can_edit(permiso racks:edit) para mostrar los botones de veredicto.- JS ES module trae datos vía
/api/racks/integrity/overview+ escucha clicks POST/ack;observatory.jsacepta?tab=integritypara aterrizar directo en la pestaña.
6. Template + CSS + JS
- HTML: markup de la pestaña en
templates/monitoring/observatory.html(dentro de{% ifmodule "integrity" %}): tabla por rack (fidelity/coverage) + histórico + cola con evidencia. Mismos msgids i18n que la página original (traducciones ES intactas). - CSS (
static/css/pages/integrity.css): Solo tokens centrales (—card-bg, —text-primary, etc.). Grid responsive. Se carga solo con el módulo activo. - JS (
static/js/pages/integrity.js): ES module,escHtml()estático para seguridad,t()en runtime para i18n. Auto-guardado: si#integrity-rootno existe (módulo apagado), no hace nada.
Flujo de uso (beta)
-
Admin añade módulo a org:
org = Organization.objects.get(pk=1) org.extra_modules.add(SaaSModule.objects.get(slug="integrity")) -
Usuario racks:edit abre Observatory → pestaña Integrity (el enlace antiguo
/integrity/redirige ahí). -
Cola de reconciliación: Cada hallazgo (device stale, undocumented, etc.) es un botón.
- Click
plan_updated→ Nota “ya corregí el plan” + 3 días silencio. - Click
real_change→ Nota “el cambio era real” + 30 días silencio. - Click
false_alarm→ Equipo va a cuarentena “muted” + 30 días silencio. No castiga fidelity.
- Click
-
Gate #202: Con cada click,
gate_stats(org)se actualiza automáticamente:signal_pct = (plan_updated + real_change) / total * 100 -
Decisión de Go/NoGo: Cuando signal_pct > 85% (ej.), la feature sale de beta → se activa en Plan 4 (Professional) o superior.
Cambios en el modelo de datos
IntegrityAck (NEW)
class IntegrityAck(models.Model):
organization: FK(Organization) # Tenant
device: FK(Device, null=True) # Para avisos de rack
profile: FK(DeviceProfile, null=True) # Para undocumented
kind: str # matched_stale | ip_conflict | undocumented
verdict: str # plan_updated | real_change | false_alarm
note: TextField # Justificación usuario
subject_label: str # Nombre legible (cacheado en caso de borrado)
muted_until: DateTimeField # Fin del período de silencio
created_by: FK(User)
created_at: DateTimeField(auto_now_add=True)
indices: [organization, muted_until]
RackIntegritySnapshot (modified v1.59.0)
Se mantuvo igual. Esta tabla almacena fotos diarias de fidelity/coverage por rack. F2 la lee para mostrar histórico.
Integración con el motor (v1.59.0)
La función compute_integrity() ahora carga veredictos vigentes al principio:
acks = _load_active_acks(organization, now) # Índice por (tipo_sujeto, id, kind)
Luego, para cada hallazgo:
ack = acks.get(("device", device.id, CLASS_MATCHED_STALE))
if ack:
finding["ack"] = {...verdict, muted_until, note...}
if ack.verdict == VERDICT_FALSE_ALARM:
counts["muted"] += 1 # No castiga fidelity
else:
counts[CLASS_MATCHED_STALE] += 1 # Sigue siendo hallazgo
fidelity_pct = 100 * matched_alive / (total - unobservable - muted)
Efecto práctico: Un false_alarm sobre “Device A” stale → Device A se excluye de fidelity durante 30 días. El plano sigue siendo inexacto, pero no penaliza la métrica.
Migraciones
core/0030_integrity_module
Seeds el módulo SaaS integrity en la tabla core_saasmodule. No lo asigna a ningún Plan.
racks/0016_integrity_ack
Crea tabla racks_integrityack con fields y índice org+muted_until.
racks/0017_integrity_ack_rls
Habilita RLS + crea política tenant_isolation con bypass admin.
Permisos y gating
- Ver pestaña + API overview: Miembro de org + módulo
integrityactivo (middleware para la API y el redirect;{% ifmodule %}para la pestaña). - POST
/ack: Además, permisoracks:edit. - RLS: A nivel PostgreSQL — usuarios nunca ven acks de otra org.
Testing
12 tests en tests/racks/test_integrity_api.py (10 de F2 + 2 de la reubicación v1.63.0):
- Gating por módulo (org sin módulo → 403;
/integrity/con módulo → 302 a la pestaña). - Render de Observatory: pestaña presente con módulo / ausente sin él.
- Veredictos y efecto en fidelity/coverage.
- Cálculo de
gate_stats(). - Validación cross-org (RLS).
- Muted_until reenters correctamente tras 30 días.
Nota CI (v1.63.0): los tests que renderizan páginas con {% vite_asset %} exigieron DJANGO_VITE dev_mode=True en config/settings/test.py — los shards de backend del CI no compilan el frontend y el manifest no existe.
Véase también
- [[entity—core—module—integrity]]
- [[entity—racks—model—integrity-ack]]
- [[entity—racks—service—integrity-motor]]
- [[entity—racks—endpoint—integrity-overview-ack]]
- [[entity—racks—view—integrity-radar]]
- [[concept—saas—multi-tenancy]]
- [[concept—racks—calibracion-sensor]]
- [[decision—20260717—integrity-apagado-por-defecto]]
- [[feature—racks—motor-integridad-f1]]
Referenciado desde
- Concepto: Calibración del motor (signal vs. noise)
- Decisión: Barra de Integridad nace APAGADA en v1.60.0
- Endpoints API: GET/overview y POST/ack (Integridad)
- Modelo: IntegrityAck (veredictos de la cola)
- Módulo SaaS: integrity (Barra de Integridad)
- Servicio: Motor de Integridad (compute + gate)
- Vista: /integrity/ (página del radar interno)