Ingress behaviours wrapped around every job handler — the scheduler arm
of the transport-neutral behaviour chain shared with the websocket,
queue, and messaging plugins (IIngressBehavior in @setu-ts/common).
Each behaviour observes an IngressContext carrying kind: 'scheduler',
the job name as name, the delivered ScheduledJob as payload, and
the 1-based attempt, and runs in declared order ahead of the handler —
INSIDE the distributed lock, so a replica that loses the lock runs no
behaviour for that fire. A behaviour that returns without calling
next() short-circuits: the handler never sees the fire. A behaviour
that throws follows the handler's own failure path — retried per the
job's RetryOptions exactly as a handler throw is. Every registration
is wrapped — imperative cron()/every()/delay() calls included — so
a mixed application cannot leave a handler unchained.
With no behaviours configured, dispatch is byte-identical to the pre-chain behaviour: the handler is handed the job directly, with no chain allocated.
When an entry is a FACTORY, dispatch is HELD until onInit has resolved
the whole chain, so nothing reaches a handler through a partial one. That
gate covers every registration — this plugin's declared entries and any a
later plugin makes imperatively through the resolved capability — which
is why no registration's timing has to change. It is released once and
costs nothing thereafter.
Instance entries are handed to the service at register(); factory
entries are resolved in the onInit phase and a throwing factory rejects
start() naming SchedulerPlugin({ behaviors }) and the entry's index
in THIS declared array.