Auditoría Suprema Terminal — Fase A Backend: Correctness/Honestidad
Resumen
Tanda de correcciones internas del backend del Agente Terminal (sesión s123, 2026-06-10). Fixes de honestidad y robustez en cuatro áreas clave:
- Endpoint
/api/agent/alertreparado — dejaba de devolver HTTP 500 perpetuo. - Promoción de Primary atómica — evita inconsistencias en la flota.
- Endurecimiento de autenticación — refresh tokens rechazados para peticiones de datos.
- Retiro de código muerto — módulo
terminal/api/scripts.pyeliminado.
Detalles por área
1. Alerta de Agente (receive_agent_alert)
Archivo: terminal/api/sentinel.py::receive_agent_alert, terminal/consumers.py::_handle_alert
Problema: Ambos endpoints intentaban crear un MonitoringAlert (modelo de configuración de reglas) con campos que no existen en el esquema:
alert_type— no es campo del modelomessage— no es campo del modeloseverity— tentativa con valor fuera de choices
Resultado: TypeError no capturado → HTTP 500 siempre, alerta perdida silenciosamente (Reglas 13/15 de honestidad).
Comprobación: El Agente actual nunca invoca este canal (send_alert existe en el código pero no se llama). Las alertas reales se derivan en el SaaS de las métricas que el Agente empuja (ver monitoring.api.evaluate_and_trigger_alerts).
Fix: Ambos receptores ahora registran un log de advertencia y responden honestamente:
- HTTP REST:
200 {"acknowledged": true} - WebSocket: log warning, sin intento de crear modelo
Nota: Si en el futuro el Agente emite alertas propias, se enrutarán por el motor real de monitorización, no por este extremo.
2. Promoción de Primary (promote_agent)
Archivo: terminal/api/fleet.py::promote_agent
Problema: La secuencia demote (Primary anterior) + promote (nuevo) se ejecutaba sin transacción:
# ANTES (sin transacción):
current_primary.role = "secondary"
current_primary.save()
agent.role = "primary"
agent.save()
Si dos requests de promoción llegaban concurrentemente, la ventana entre reads y writes podía dejar:
- 0 Primary (ambos demotaban y ninguno se elevaba)
- 2 Primary (ambos se promovían sin ver el cambio del otro)
Fix: Serialización con transaction.atomic() + select_for_update():
with transaction.atomic():
agent = AgentInstance.objects.select_for_update().filter(...).first()
current_primary = AgentInstance.objects.select_for_update().filter(...).first()
# demote + promote bajo el lock
...
# Notificaciones WS fuera del lock (no retener la transacción durante I/O de red)
_send_role_change_bg(current_primary.agent_id, "secondary")
_send_role_change_bg(agent.agent_id, "primary")
Ahora la reasignación es atómica dentro de la organización.
3. Endurecimiento de Autenticación
Archivo: terminal/api/auth.py::get_agent_from_request
Problema: Un refresh token (larga vida, 180 días) podía autenticar peticiones de datos del Agent.
# ANTES:
payload = verify_agent_token(token)
return payload # sin revisar tipo
Un refresh token tiene type="refresh" en su payload. Un agente malintencionado con acceso a un refresh token podía usarlo para solicitar datos hasta 180 días después (vuln SA2 BAJA).
Fix: Ahora verifica explícitamente type="access":
payload = verify_agent_token(token)
if not payload or payload.get("type") != "access":
return None
return payload
Access tokens (corta vida, ~15 min) son los únicos válidos. Los genera generate_agent_token().
4. Código Muerto: terminal/api/scripts.py
Archivo: Eliminado terminal/api/scripts.py + su re-exportación en __init__.py
Contexto: El router de Scripts SSH estaba expuesto en terminal/api/__init__.py pero nunca se montaba en las URLs. El punto de entrada real es network.api.scripts_router en /api/scripts.
Impacto: El modelo terminal.models.Script se conserva (lo usan backup/restore). Solo se retira el endpoint fantasma y el módulo redundante.
Cambios Operacionales
- Migraciones: ninguna.
- Tests: 6 nuevos en
tests/api/test_terminal_agent_api.py(auditoría s122 → rectificación s123). - Diferido:
- Carrera de doble-Primary en
consumers._register_and_assign_rolerequiere migración + dedup. UniqueConstraintparcial de Primary es endurecimiento a considerar.
- Carrera de doble-Primary en
Reglas de Honestidad Tocadas
- Regla 13 (sin silencio): alerta registrada, no perdida.
- Regla 15 (confirmación clara): endpoint responde 200 {“acknowledged”: true}, no miente con 500.
Véase también
- [[entity—terminal—model—agent-instance]]
- [[concept—saas—multi-tenancy]]
- [[entity—monitoring—endpoint—evaluate-and-trigger-alerts]]
- [[feature—terminal—agent-authentication]]
- [[entity—terminal—endpoint—receive-agent-metrics]]
- [[concept—reliability—atomicity]]