What every library-provided Oban job needs from its arguments.
A plan is built from resource modules at runtime, so it cannot ride in a job
argument — Oban serialises args to JSON, and a %Plan{} is neither JSON nor
cheap to rebuild blindly. So a job carries the name of the function that
builds one, and this resolves it.
Shared rather than duplicated because two workers now need it (ScanWorker
and ReprocessWorker), and a divergence between them would be the kind of bug
that only appears on the job that runs least often.
Summary
Functions
The recompute:/key_rule: a job drains with, defaulting to the Node
strategies a DSL-authored graph uses.
A module name from a job argument, with or without the Elixir. prefix.
@spec plan(map(), module()) :: ReactiveDag.Plan.t()
The plan for a job: its own "plan_mfa" argument, else the configured default.
Raises with the fix rather than a MatchError, because "no plan" is a
configuration mistake and the message is the only place it can be explained.