ADR-018: marca de sincronización de cada fuente para calcular `asOf` y `stale`
Aceptado el 10/11/2026 por Andrés.
Contexto
docs/to-be/05-modelo-de-lectura.md dice que toda respuesta de stock y precio lleva asOf y que, si el dato supera el umbral (stock 5 minutos, precio 30 minutos), sale stale: true. El verificador F10 de F3 lo exige: con el ERP caído, primero 200 y luego stale: true.
El diseño no dice qué es asOf para un dato que no cambia. Si es el momento del último cambio de la llave, un precio estable desde hace tres días sale siempre stale: true aunque el ERP esté sano, y el indicador deja de servir. Si es el momento en que el servicio leyó el evento, el problema es el mismo. Lo que el canal necesita saber es otra cosa: hasta qué momento la capa sabe que ese dato sigue siendo el del sistema de origen.
Esa información la tiene solo el adaptador: sabe hasta qué posición leyó el feed del ERP o del WMS y cuándo lo consultó con éxito por última vez. Hoy no la publica.
Opciones
| Opción | A favor | En contra |
|---|---|---|
| A. Cada adaptador publica una marca de sincronización por fuente en un tópico compactado | Un evento pequeño cada pocos segundos; asOf y stale quedan bien definidos; la caída del legado se nota sin que nadie consulte al adaptador |
Un canal y un mensaje nuevos en el contrato; dos servicios más lo consumen |
B. asOf igual al último cambio de la llave |
Sin trabajo | Datos estables marcados como desactualizados; F10 no distingue un ERP caído de un precio que no cambió |
| C. pricing e inventory consultan la salud del adaptador por HTTP | Sin contrato de eventos nuevo | Llamada síncrona interna que la regla 2 no permite; acopla la lectura al adaptador |
| D. El adaptador republica todas las llaves cada cierto tiempo | Sin mensaje nuevo | 50.000 SKU por canales y ubicaciones (supuesto 4): carga sobre Kafka y sobre el legado sin valor |
Decisión
Opción A.
- Canal nuevo
andes.sources.sync.v1, compactado, clavesystem(erp,wms). MensajeSourceSyncedconsystem,position(lsn del ERP o seq del WMS) ysyncedAt(UTC). - El adaptador lo publica tras cada sondeo exitoso, y solo después de que Kafka confirmó los eventos de los cambios hasta esa posición. Como máximo uno cada 15 segundos (supuesto: 20 marcas dentro del umbral de stock de 5 minutos; no cambia la frescura de los cambios, que viajan por su propio evento). Si el legado no responde o el circuito está abierto, no publica.
- pricing-service consume la marca del ERP; inventory-service, la del WMS.
asOfde una llave es el más reciente entre el momento del dato y elsyncedAtde su fuente.staleesnow - asOfmayor que el umbral. - Con el ERP caído, la marca deja de avanzar y, pasado el umbral, todas las respuestas de precio salen con
stale: true. Eso es F10.
Verificación
- Prueba de cada adaptador: con el mock arriba publica marcas que avanzan; con el mock caído deja de publicarlas.
- Prueba de pricing e inventory: un dato sin cambios con marca reciente sale
stale: false; sin marca nueva durante más que el umbral,stale: true. - F10 de F3 con el escenario de ERP caído.
Qué se sacrifica
- Un canal, un mensaje y dos consumidores más en el contrato.
asOfdeja de ser el momento del último cambio: pasa a ser hasta cuándo se sabe que el dato está vigente. Hay que decirlo en el contrato para que el canal no lo lea como fecha de modificación.- Entre la marca y el último evento puede haber unos segundos en que una llave parece al día con un cambio todavía en camino. Es menor que el objetivo de frescura y la verdad se comprueba al reservar.