The source invoke's two calls, as a host's invoke handler makes them:
start/3 turns a %Statifier.Effect.Invoke{} into
StatifierRouter.subscribe/3, and cancel/3 turns the
%Statifier.Effect.CancelInvoke{} the engine emits on state exit into
StatifierRouter.cancel/2 (ADR-0007, section 2). An invocation's
lifetime is a subscription's, and the chart writes no cleanup for it.
The chart's half is one element, whose params name the binding and nothing else (ADR-0007, section 1):
<invoke type="myapp:source">
<param name="binding" expr="'clicks_to_join'"/>
</invoke>The type is the host's own string, registered in the
Statifier.Invoke.Types.t/0 snapshot it stamps; this module never
reads it, so a host may serve as many source invoke types as it likes
with one delegate.
Why this is a delegate and not a @behaviour
It does not implement Statifier.Invoke.Handler (statifier 2.6.0,
the version mix.lock resolves), and it cannot. That behaviour's
Statifier.Invoke.Handler.start/2 and
Statifier.Invoke.Handler.cancel/2 are pure planning callbacks,
called from Statifier.Session.Effects.plan/2's own fold with "no
process, no clock, and no I/O"; they return instructions for an executor
to perform. Subscribing writes a row, so it belongs in the impure half. The
callbacks also carry no slot for it: the plan context is
%{session_id: _, invoke_types: _, invoke_handlers: _} and carries "no
pid, no %MachineState{}, and no session struct", so neither a
StatifierRouter.Config nor the execution id can reach a planning
callback at all.
So a host that runs a live Statifier.Session writes a handler whose
Statifier.Invoke.Handler.start/2 returns
{:ok, [{:handler, __MODULE__, payload}]} and whose
Statifier.Invoke.Handler.perform/2 calls start/3 here. A durable
host - the mode ADR-0007, section 5 specifies, and the only one - has an
executor rather than a session: both effects arrive at the executor seam
this package already hands StatifierPersistence.Executions.create/4
and step/5, where the execution id is in the context and the
configuration is in hand, and that handler calls straight into these two
functions.
Idempotency
Both calls are idempotent, which is the contract either door needs.
Statifier.Invoke.Handler says a Statifier.Invoke.Handler.perform/2
"MUST be idempotent on invoke_id", because a host that crashes
between performing an instruction and recording that it ran replays the
same drive; and it says a cancel "MAY be planned for an invocation a
host has already reported complete", so a handler "MUST tolerate
cancelling an invoke_id it no longer knows". start/3 answers
{:ok, :already_subscribed} for the second call and cancel/3
{:ok, :not_subscribed}; neither is an error.
What it refuses
An execution created under :always_new has no address row, so it has
no key to subscribe under and its source invoke is refused rather than
subscribed under an invented key (ADR-0007, section 6). That reaches a
caller as {:error, {:unaddressed_execution, execution_id}} from
StatifierRouter.subscribe/3. An invoke whose params do not carry a
binding is refused here, before any read.
Summary
Types
Functions
@spec cancel( StatifierRouter.Config.t(), String.t(), Statifier.Effect.CancelInvoke.t() ) :: {:ok, :cancelled | :not_subscribed}
Cancels the subscription the matching start/3 created, naming it by
execution_id and the cancellation's invoke_id.
The engine's %Statifier.Effect.CancelInvoke{} carries an invoke_id
and the exiting state's index and no binding - deliberately, since
widening that callback was refused upstream - so this reads the binding
back off the subscription row before calling
StatifierRouter.cancel/2. A cancellation for an invocation with no row
is {:ok, :not_subscribed}.
@spec start(StatifierRouter.Config.t(), String.t(), Statifier.Effect.Invoke.t()) :: {:ok, :subscribed | :already_subscribed} | {:error, refusal()}
Subscribes execution_id to the binding named by invoke's binding
param, under invoke's own invoke_id.
Returns what StatifierRouter.subscribe/3 returns, or
{:error, {:missing_binding_param, params}} when the invoke carries no
binding param of its own. Statifier.Effect.Invoke's params is the
resolved <param>/namelist payload as a string-keyed map, or
:undefined when the element has none (statifier 2.6.0).