Envelope estable

Errores HTTP

Sobre HTTP, el envelope de EVA es siempre el mismo. No hay 202. Validación de query/body es 422, no 400. Miss o permiso cruzado es 404 (no se filtra existencia a otros tenants). 304 en extract es revalidación correcta (If-None-Match), no un error.

{
  "code": "INVALID_TOKEN",
  "message": "Invalid token.",
  "details": []
}

Éxito

HTTPOperación
200Índice, extract GET, extract batch, pending, claim, heartbeat, result, CSV ingest
304Extract: If-None-Match coincide. Cuerpo vacío. No es un error.
201JSON ingest (hallazgo o aspecto); ensure si creó proyecto o enlazó escenario

Autenticación y tenant

HTTPcodeCuándo
401MISSING_BEARER_TOKENFalta Authorization: Bearer
401INVALID_TOKENPAT inválido, caducado o revocado
401TOKEN_TYPE_NOT_ALLOWEDJWT de sesión en rutas PAT, o PAT en CRUD de tokens
403TENANT_MISMATCHX-Tenant-Id ≠ tenant del PAT
403INTEGRATION_SCOPE_DENIEDFalta extract, ingest, retest_read o retest_write
403INTEGRATION_IP_DENIEDExtract/ingest: la IP no cae en allowed_cidrs del PAT. Retest no aplica esta lista.

Recurso y conflicto

HTTPcodeCuándo
404NOT_FOUNDProyecto/escenario inexistente o sin permiso (extract/ingest)
404JOB_NOT_FOUNDUUID de job desconocido en ese tenant
409SCENARIO_AMBIGUOUSMás de un escenario con ese scenario_key en el proyecto o en catálogo (ensure)
409PROJECT_CODE_CONFLICTEnsure: el código existe con otro nombre (no se renombra)
409PROJECT_CLIENT_CONFLICTEnsure: el código existe con otro client_code
409JOB_ALREADY_CLAIMEDLease vigente de otro runner_id
409JOB_NOT_CLAIMEDHeartbeat/result sobre job pending
409JOB_LEASE_EXPIREDLease vencido o job expired
409JOB_ALREADY_FINISHEDJob succeeded / failed
422validaciónQuery o body inválido (Pydantic). No use 400. CIDR inválido al crear el PAT. Índice: fechas invertidas/inválidas. Batch: más de 10 ítems, report_type ausente, pares duplicados.
429INTEGRATION_RATE_LIMITEDExtract/ingest: cupo 60/min por token_id+superficie. Índice y GET extract = 1 hit; batch = N. Cabecera Retry-After. Retest no usa este cupo.

Matriz de laboratorio

CasoEsperado
Sin Authorization401 MISSING_BEARER_TOKEN
JWT de sesión en extract/ingest/retest401 TOKEN_TYPE_NOT_ALLOWED
PAT sin scope ingest en ingest403 INTEGRATION_SCOPE_DENIED
Extract/ingest desde IP fuera de allowed_cidrs403 INTEGRATION_IP_DENIED
N+1 extract del mismo PAT en un minuto429 INTEGRATION_RATE_LIMITED + Retry-After
Retest pending con CIDR o cupo extractNo aplica. Solo PAT + scope retest_read.
X-Tenant-Id de otro tenant403 TENANT_MISMATCH
UUID de otro tenant404
report_type inválido422
Índice con updated_since > updated_until422
Batch con 11 ítems o sin report_type422
Batch con un UUID inexistente200 y ese ítem not_found (no 404 de lote)
PAT revocado, mismo curl401 INVALID_TOKEN
Extract con If-None-Match del ETag actual304 (éxito; cuerpo vacío)

El cupo de 60/min y allowed_cidrs aplican a extract e ingest. Índice y GET extract cuentan 1; un POST extract-batch cuenta N. Un CSV es 1 POST. Retest no usa este cupo. Envelope {code,message,details}.

Clases del runner (no son HTTP de EVA)

El contenedor clasifica fallos de operativa en logs y en /health last_class. No son el envelope {code,message,details} ni viajan en POST …/result salvo el set cerrado de error_code.

Class localCuándoQué hace el agente
auth_failed401 INVALID_TOKEN / MISSING_BEARER_TOKEN/ready 503. Backoff ~60 s. No martilla pending cada 15 s.
eva_unreachableTimeout, DNS, connection refused o 5xx hacia Flask/health 200, /ready 503. Backoff 5→60 s. No es error_code de result.
claim_conflict409 JOB_ALREADY_CLAIMEDSiguiente tick. No es error de health.
lease409 lease / finished en heartbeat o resultLog warning. 409 JOB_ALREADY_FINISHED en un retry de result cuenta como éxito.

Detalle y JSON de /health: Runner.