Pre-flight checks — validando el laboratorio antes de las ejecuciones¶
Estado del alcance (post-congelación de alcance 2026-05-10) — Ver ARCHITECTURE.md para los 37 MÓDULOs canónicos + 7 Test Kinds + arquitectura de safety DOM/CPOS/PIE-PA. ADRs 0014, 0019-0025 cubren adiciones post-Freeze.
El motor de Pre-flight checks es un validador read-only que se ejecuta contra los dispositivos DUT API registrados ANTES de que el operador dispare la ejecución de un Test Plan. Detecta tempranamente las condiciones de "lab fuera del estado esperado" para que las ejecuciones no produzcan datos forensemente inútiles.
El principio: garbage in, garbage out. Si el NGFW tiene cambios de deploy pendientes, o la política de decrypt está apagada cuando el plan exige decrypt-on, los números de p99 resultantes no dicen nada útil. El pre-flight rehúsa iniciar la ejecución en lugar de permitir que produzca datos engañosos.
Cómo encaja en el flujo del operador¶
Operator picks plan → Runs preflight → Reviews failures → Fixes lab state →
Triggers snapshot → Re-runs preflight → All green → Starts the actual test run
El pre-flight es invocado manualmente hoy (POST endpoint). En un PR futuro (PR-D), el motor de Test Plan condicionará el run-start a un preflight exitoso automáticamente.
API¶
POST /api/test-runs/preflight¶
curl -X POST "https://dashboard.example/api/test-runs/preflight" \
-H "Authorization: Bearer $ADMIN_TOKEN" \
-H "content-type: application/json" \
-d '{"planIdentifier": "BASELINE-SLO-30M"}'
Respuesta (éxito — 200):
{
"planIdentifier": "BASELINE-SLO-30M",
"ranAt": "2026-05-06T14:35:00.000Z",
"checksRun": 8,
"checksPassed": 8,
"checksFailed": 0,
"checksSkipped": 0,
"pass": true,
"checks": [
{
"checkId": "ngfw-deploy-clean",
"description": "NGFW deploy state is DEPLOYED — no pending config changes",
"deviceHostname": "ftd-1.lab.example.com",
"vendor": "cisco-ftd",
"pass": true,
"detail": "state=DEPLOYED — no pending changes",
"evidence": {
"snapshotId": "...",
"payloadSha256": "abc123...",
"collectedAt": "2026-05-06T14:34:42.000Z"
}
},
...
],
"summary": "8/8 checks passed — lab is ready for plan BASELINE-SLO-30M"
}
Respuesta (algún check falló — 422):
{
"planIdentifier": "BASELINE-SLO-30M",
"ranAt": "2026-05-06T14:35:00.000Z",
"checksRun": 8,
"checksPassed": 6,
"checksFailed": 1,
"checksSkipped": 1,
"pass": false,
"checks": [
...
{
"checkId": "ngfw-decrypt-state-matches-plan",
"deviceHostname": "ftd-1.lab.example.com",
"vendor": "cisco-ftd",
"pass": false,
"detail": "plan requires decrypt-on but no decrypt rules configured",
"evidence": { ... }
}
],
"summary": "1 check(s) failed, 1 skipped (missing snapshots) — review details"
}
Catálogo de checks (actual)¶
| Check ID | Aplica a | Endpoint label | Qué valida |
|---|---|---|---|
ngfw-deploy-clean |
Cisco FTD | deploy_status |
state == 'DEPLOYED' (sin cambios pendientes) |
ngfw-decrypt-state-matches-plan |
Cisco FTD + Fortinet | decrypt_policy |
Si el plan exige decrypt-on, al menos una regla configurada; si decrypt-off, cero reglas |
ntp-source-configured |
Todos los vendors | ntp_config |
El dispositivo tiene al menos un servidor NTP configurado |
ngfw-ha-state-sane |
Cisco FTD | ha_status |
El estado HA es uno de ACTIVE / STANDBY_READY / NEGOTIATION / JOIN |
snapshot-fresh |
Todos los vendors | system_info |
El snapshot más reciente tiene menos de 60 min |
El catálogo es extensible — agregar un nuevo check es anexar una entrada en lib/preflight/checks.ts. Sin cambios en el engine.
Qué retornan los checks¶
Cada check retorna uno de tres estados:
| Resultado | Significado | Acción del operador |
|---|---|---|
| pass: true + evidence | Check evaluado contra un snapshot, todo bien | Ninguna — proceda |
| pass: false + evidence | Check evaluado contra un snapshot, FALLO | Corrija el estado del dispositivo, dispare un snapshot manual, re-ejecute |
| pass: false + evidence: null | Skipped — no existe snapshot para este dispositivo + label | Dispare un snapshot manual primero |
Evidence es el campo más importante — cita el SHA-256 exacto del snapshot + collected_at. El mismo SHA-256 aparecerá en los anexos del Test Run Report cuando se entregue PR-D, así el chain-of-custody queda intacto.
Por qué importa el pre-flight¶
Sin pre-flight, este es un escenario típico:
El operador dispara BASELINE-SLO-30M esperando decrypt-on. El NGFW tuvo su política de decrypt deshabilitada por otro ingeniero hace 30 minutos. La ejecución de 30 minutos completa; el p99 luce sospechosamente bajo. El operador solo nota que algo anda mal al comparar con la ejecución de la semana pasada. La run es inválida. El engagement pierde 30 min + la credibilidad del informe.
Con pre-flight:
El operador corre preflight primero. El check
ngfw-decrypt-state-matches-planfalla: "plan requires decrypt-on but no decrypt rules configured". El operador abre la consola del NGFW (o dispara una write-op vía la API futura), habilita decrypt, snapshot, re-corre preflight, todo en verde, inicia la run. Los 30 minutos se gastan en una ejecución válida.
Flujo del operador — secuencia completa¶
# 1. Confirm devices are registered
curl -H "Authorization: Bearer $ADMIN_TOKEN" \
"https://dashboard.example/api/admin/dut/devices"
# 2. Trigger fresh snapshots (so preflight sees current state)
for id in $(curl -s -H "Authorization: Bearer $ADMIN_TOKEN" \
"https://dashboard.example/api/admin/dut/devices" \
| jq -r '.devices[].id'); do
curl -X POST -H "Authorization: Bearer $ADMIN_TOKEN" \
"https://dashboard.example/api/admin/dut/devices/$id/snapshot"
done
# 3. Run pre-flight
curl -X POST "https://dashboard.example/api/test-runs/preflight" \
-H "Authorization: Bearer $ADMIN_TOKEN" \
-H "content-type: application/json" \
-d '{"planIdentifier": "BASELINE-SLO-30M"}'
# 4. If pass: true → proceed with run trigger (existing flow)
# 5. If pass: false → review checks[].detail, fix the lab, go to step 2
Agregando un nuevo check¶
El catálogo de checks en lib/preflight/checks.ts es data-driven. Para agregar un check:
{
id: 'my-new-check',
description: 'What this check validates in operator-friendly prose',
endpointLabel: 'system_info', // which snapshot label the check needs
appliesTo: (device, plan) => {
// Return true if this check is relevant for this device + plan combo
return device.vendor === 'cisco-ftd' && plan.someField === 'someValue';
},
evaluate: (snapshot, plan) => {
// Inspect snapshot.payloadJson and decide pass/fail
const fieldValue = (snapshot.payloadJson as any)?.someField;
if (fieldValue === 'expected') {
return { pass: true, detail: 'value matches expectation' };
}
return { pass: false, detail: `value=${fieldValue}, expected 'expected'` };
}
}
Sin cambios en el engine. El runner descubre el nuevo check automáticamente.
Limitaciones¶
Alcance honesto:
- Read-only — el pre-flight NO dispara snapshots por sí mismo. El operador los dispara vía el endpoint existente
POST /api/admin/dut/devices/{id}/snapshot - Snapshot stale tolerado hasta 60 min — el check
snapshot-freshfalla si es más antiguo. Ajuste editando el check, O corra un snapshot fresco antes del preflight - Sin run-blocking automático aún — PR-D integrará el preflight al flujo de run-start del Test Plan (rehusará iniciar si el preflight falla)
- Checks de Cisco UCS aún no conectados — el adapter UCS está en la cola del PR #199. Cuando se mergee, se agregarán checks específicos de UCS (sin critical faults, thermal sane)
- Sin write/remediation — el pre-flight reporta estado pero no lo corrige. F-1 / F-2 (write ops) en el API_FEATURE_CATALOG.md cubren esa capacidad futura
Lo que el pre-flight NO reemplaza¶
- El juicio del operador para preocupaciones no-checables (conexiones de cable, capa física, contratos de soporte de vendor)
- El TLS Decrypt Mode Probe (que es independiente del estado de la API — el probe puede detectar "decrypt está configurado pero de algún modo no está realmente desencriptando tráfico", lo que los checks solo-de-API no pueden)
- Verificación de time-sync (script separado
check-time-sync.sh— el pre-flight eventualmente lo llamará como un check)
Relacionados¶
DUT_API_INTEGRATION.es.md— qué integración de API consumen los checksDUT_API_OPERATIONS.es.md— cómo registrar dispositivos que el pre-flight inspeccionaAPI_FEATURE_CATALOG.es.md— los pre-flight checks corresponden a los ítems de la categoría A, A-1 a A-7TEST_PLANS.es.md— los plans contra los cuales el pre-flight validaTIME_SYNC.es.md— gate de time-sync separado; el pre-flight lo integrará en un follow-up