Bus::batch, and the completion callback that is not a job

A batch gives progress and a completion hook for a set of jobs, and the callbacks run on the worker that finished the last one.

Bus::batch($chunks->map(fn ($ids) => new ExportChunk($ids)))
    ->then(fn (Batch $b) => ExportReady::dispatch($b->id))
    ->catch(fn (Batch $b, Throwable $e) => report($e))
    ->finally(fn (Batch $b) => Cache::forget("export:{$b->id}"))
    ->allowFailures()
    ->dispatch();

The callbacks are serialised closures stored with the batch, which means they cannot capture anything unserialisable and cannot be changed after dispatch — a deploy that removes the class a closure references leaves a batch that cannot complete. Dispatching a job from then rather than doing the work inline is the shape that survives, because the callback runs on a worker with no retry semantics of its own.