Skip to main content
Traducción. Autoritativo: fs-core-0006-event-ingestion.md.

Contexto

Esta es la puerta de entrada de toda la plataforma y el endpoint que toda integración usa primero. Su latencia es lo primero que mide un desarrollador, y se sitúa dentro del checkout de otra persona, así que el presupuesto no es negociable. ADR-013 resuelve la tensión entre “responder rápido” y “hacer mucho trabajo” con fast-ack: autenticar, validar, limitar, encolar, responder 202. El camino pesado —resolución de identidad, evaluación de segmentos, matching de reglas— ocurre detrás de la cola. El costo es que las lecturas son eventualmente consistentes, y el contrato lo dice explícitamente respondiendo 202 y no 200. Los reintentos están garantizados, porque las redes fallan. La idempotencia no es entonces una optimización sino la condición de corrección.

Alcance (normativo)

  • POST /v1/core/track, /identify y /batch con fast-ack.
  • core.tracked_events, append-only, particionada RANGE mensual.
  • Clave de idempotencia por tenant, deduplicando en la valla de almacenamiento.
  • Rate limiting en Upstash, por tenant y por key.
  • El procesador: resolución de identidad, append y entrega a evaluación de segmentos y reglas.
  • Retención por plan con exportación a Storage antes de soltar una partición.
  • core.event.tracked en el outbox.

Fuera de alcance (normativo)

  • La evaluación de segmentos en sí — FS-CORE-0008.
  • El matching de reglas — FS-LOY-0004.
  • Write keys públicas para navegador — F1b, con el widget (DEC-D6). F1a es solo server-side.
  • Aliases estilo Segment. La ruta canónica es por módulo (DEC-D2).

Comportamiento (normativo)

  1. El endpoint hace cinco cosas y ninguna más: autenticar, validar con Zod, limitar, encolar y responder 202 con el id del evento. p95 <100 ms. PROHIBIDA: cualquier lógica de negocio en este camino.
  2. La respuesta es 202, nunca 200. El código de estado es el contrato de que el procesamiento aún no ocurrió, y es lo que impide que un integrador escriba una suposición de read-after-write.
  3. idempotency_key es único por tenant. Un duplicado devuelve 202 con el id original y no crea ninguna segunda fila ni segundo efecto aguas abajo.
  4. occurred_at lo aporta el llamador con su zona horaria; received_at es nuestro. Se guardan ambos — un punto de venta que estuvo offline una hora no debe ver sus eventos reordenados.
  5. El procesador es idempotente por core.processed_jobs y por la clave del evento. Dos vallas, porque este es el camino donde un duplicado cuesta puntos.
  6. La resolución de identidad corre primero: un identificador desconocido crea un contacto anónimo en vez de descartar el evento.
  7. tracked_events es append-only y particionada mensualmente. Toda consulta lleva la clave de partición — una consulta solo por contact_id se rechaza en revisión y por el lint de CI.
  8. Las propiedades del evento son JSONB, validadas contra la taxonomía instalada donde aplique (FS-CORE-0012) y aceptadas permisivamente donde no.
  9. Retención por plan: 13 / 25 / 37+ meses. Una partición se exporta a Storage como NDJSON comprimido antes de soltarse, nunca simplemente se suelta.
  10. Los rate limits se publican por plan (DEC-D4) y se devuelven en cabeceras RateLimit-*.

Datos (normativo)

API (normativo)

Eventos (normativo)

core.event.tracked en el outbox después de que el procesador agrega la fila. Disponible como webhook saliente, que es como un tenant espeja su propio flujo de eventos hacia su warehouse.

Criterios de aceptación (normativo)

  1. track p95 <100 ms bajo el perfil k6 al throughput objetivo documentado.
  2. La misma idempotency_key enviada 100 veces produce una fila y un efecto aguas abajo.
  3. Eventos con occurred_at una hora en el pasado caen en la partición correcta y se ordenan por occurred_at, no por llegada.
  4. Un batch de 500 se acepta; 501 se rechaza con error tipado.
  5. Visibilidad del efecto end to end <5 s p95: evento entra, puntos acreditados, notificación encolada.
  6. Un test sintético de cambio de mes deja vacía la partición DEFAULT.
  7. La exportación de retención produce un NDJSON legible con checksum verificado antes de soltar la partición.
  8. Negativo: una consulta a tracked_events sin predicado de tiempo falla el lint de CI.

Ejecución

Pipeline de eventos asíncrono — el arquetipo canónico. Endpoint en backend/api, procesador en backend/workers, jobs de retención y particiones en backend/scheduler. Esto es TS-003, y trackEvent (FS-LOY-0004 + TS-004) lo completa.

Preguntas abiertas

Changelog

Registro de entrega

Aún no implementado.