job.completed or one job.failed, exactly once per job outcome. These notifications are a mandatory product contract — the worker refuses to boot with streaming disabled rather than silently swallowing them.
Events travel in the shared CloudEvents envelope, published over RabbitMQ: each event routes to the exchange named by RABBITMQ_JOB_EVENTS_EXCHANGE with the event key as the AMQP routing key (job.completed, job.failed). Both events are outbox-backed: the outbox row is written durably and a relay publishes it, retrying through broker outages; a repairer re-emits terminal events that never reached the broker. Delivery is at-least-once — deduplicate on ce-id, which is deterministic per job outcome: fetcher.job.<status>.<jobID>, so every re-emission of the same fact carries the same id. ce-subject is the job id; single-tenant deployments stamp ce-tenantid as single-tenant.
Unlike most Lerian payloads, Fetcher’s job events use camelCase keys — they mirror Fetcher’s REST surface.
Job events
The
result block describes the produced artifact: where it was written, its size and row count, the output format, and — when result protection is enabled — the integrity (HMAC) and protection descriptors a consumer uses to verify the artifact before trusting it.

