External effect handler — invoked at drain time with resolved params and
a context. Returning outputs records them on the run's effectHistory
row, read downstream as $effects['<name>'].<output>. Returning ops
applies the state half of the effect in the completion commit (field.*
only, never status.set) — every returned op must explicitly set
target.scope, since completion ops have no authoring location to infer
one from. Throwing marks the effect failed, with no ops.
Delivery is at-least-once: a handler MAY run more than once for the same
effect (a dispatch dying after its side effect but before commit, or a
lease expiring mid-dispatch and being taken over). Completion is
first-writer-wins — the losing run's completion is reported as lost.
Write handlers to tolerate this: check effectHistory[] for a row keyed
by ctx.effectKey before irreversible work, and derive external
identifiers from ctx.effectKey so the receiving system can dedupe.
The bivarianceHack indirection keeps this readable from the non-generic
Engine surface; only the typed drain invokes handlers.
External effect handler — invoked at drain time with resolved
paramsand a context. Returningoutputsrecords them on the run'seffectHistoryrow, read downstream as$effects['<name>'].<output>. Returningopsapplies the state half of the effect in the completion commit (field.*only, neverstatus.set) — every returned op must explicitly settarget.scope, since completion ops have no authoring location to infer one from. Throwing marks the effect failed, with noops.Delivery is at-least-once: a handler MAY run more than once for the same effect (a dispatch dying after its side effect but before commit, or a lease expiring mid-dispatch and being taken over). Completion is first-writer-wins — the losing run's completion is reported as
lost. Write handlers to tolerate this: checkeffectHistory[]for a row keyed byctx.effectKeybefore irreversible work, and derive external identifiers fromctx.effectKeyso the receiving system can dedupe.The
bivarianceHackindirection keeps this readable from the non-genericEnginesurface; only the typed drain invokes handlers.