Skip to main content
Traducción. Autoritativo: fs-core-0007-job-infrastructure.md.

Contexto

Toda cola es at-least-once. Un worker va a procesar el mismo job dos veces —tras un timeout, un deploy, un reintento de proveedor— y en un sistema que acredita puntos, eso es dinero. La idempotencia no es un atributo de calidad aquí; es la condición de corrección, y por eso DEC-C6 hizo processed_jobs obligatorio para todo consumidor en vez de una elección por worker. La segunda mitad es la garantía de evidencia de DEC-C5: toda falla y todo agotamiento de reintentos queda persistido, no solo registrado en logs. Una línea de log rota; una fila de dead_letters es algo que un operador puede encontrar, entender y reprocesar. Los éxitos a volumen son métricas, no filas — la asimetría es deliberada.

Alcance (normativo)

  • core.processed_jobs: la valla de deduplicación, escrita dentro de la transacción de trabajo.
  • core.dead_letters: fallas con payload, intentos, último error y estado de revisión.
  • El harness de consumidor: valla de idempotencia, reintentos con backoff y jitter, escritor de dead letters.
  • Limpieza por TTL de processed_jobs.
  • Alertas a BetterStack ante dead letters nuevos.
  • Replay desde Softcrum Ops, republicando con el mismo job_id.

Fuera de alcance (normativo)

  • QueuePort y sus adaptadores — TS-002.
  • Los carriles de notificación — son de messaging, aunque usan este harness.
  • Una UI de replay — frontend/ops.

Comportamiento (normativo)

  1. Un consumidor verifica e inserta en processed_jobs dentro de la misma transacción que su trabajo. Fuera de ella, una caída entre ambos deja el job marcado como hecho sin nada hecho.
  2. Un job_id duplicado hace ack y salta. En silencio, con una métrica — un duplicado es operación normal, no un error.
  3. Política de reintentos: 5 intentos, backoff exponencial con jitter, base 30 s, tope 1 h. Sobreescribirla por cola exige justificación en el spec de ese módulo.
  4. Los reintentos agotados y las fallas duras escriben una fila de dead_letters con el payload completo, el número de intentos, el último error y el correlation_id, y alertan a BetterStack.
  5. Los mensajes envenenados se saltan los reintentos por completo. Un payload inválido de schema nunca se volverá válido; reintentarlo cinco veces son cinco minutos y cinco alertas idénticas desperdiciados.
  6. El replay republica con el mismo job_id, de modo que processed_jobs garantiza que no haya doble efecto. Esa propiedad es lo que hace al replay seguro para usarlo en masa.
  7. Las filas de processed_jobs expiran por TTL; el TTL debe exceder con margen la ventana máxima de reintentos, o un reintento tardío tras la expiración volvería a aplicar el trabajo.
  8. Un dead letter pasa pending_review → replayed | discarded, y descartar exige una razón.
  9. Los éxitos de alto volumen son métricas, no filas. Escribir una fila por job exitoso haría de esta tabla la más grande de la plataforma sin beneficio.

Datos (normativo)

API (normativo)

En la práctica son de Ops: un tenant no administra nuestras colas.

Eventos (normativo)

Ninguno en el outbox. Los dead letters alertan por observabilidad, no por eventos de dominio.

Criterios de aceptación (normativo)

  1. El mismo job_id procesado dos veces en paralelo produce exactamente un efecto.
  2. Una caída entre el trabajo y la valla no deja ninguno de los dos.
  3. Una falla transitoria reintenta 5 veces con intervalos crecientes y luego escribe un dead letter.
  4. Un payload inválido de schema va directo a dead letters con cero reintentos.
  5. El replay de un dead letter con el mismo job_id no produce un segundo efecto.
  6. La limpieza por TTL borra filas expiradas y nunca una dentro de su ventana de reintentos.
  7. Descartar sin razón se rechaza.
  8. Negativo: ningún job exitoso escribe una fila más allá de su valla de processed_jobs.

Ejecución

Un solo slice, comando síncrono. Schema en TS-001; el harness en TS-002 junto a QueuePort. Runbook: ../../../runbooks/dead-letter-replay.md.

Preguntas abiertas

Changelog

Registro de entrega

Aún no implementado.