Statifier.Effect.Invoke (Statifier v2.5.0)

Copy Markdown View Source

Payload for {:invoke, %__MODULE__{}} - spec 6.4's <invoke>. Fields are the element's own attribute names: invoke_id (id/idlocation, generated when the element has none), type, src, params (the resolved <param>/namelist payload), content (the resolved <content> payload), autoforward (the autoforward attribute).

<invoke> is attached to a state, not to a block of executable content, so this payload carries state_index - the invoking state's index (constraint 3) - rather than a c_index; there is no content node to identify. state_index is named apart from src (spec 6.4's URI attribute) on purpose - the two fields are two letters apart with unrelated meanings, and the original source name invited confusing them (post-review correction). macrostep/microstep/round are the counters as they stand when the invoke is produced.

invoke_index is the invoking state's invoke list position (document order) - the second half of Statifier.Event.Cause.origin()'s {:invoke, state_index, invoke_index} identity, which the session needs to name the failing element in an error.communication raised when it cannot start the child (spec 3.12.2). invoke_one/6 already has the value in scope when it builds this struct; it was dropped rather than carried until this payload had a caller for it, the same "no dead field" discipline origin/sendid on Statifier.Event followed.

src and content are two fields, not one

Spec 6.4 collapses the data channels an invoked service can receive to two pairs treated identically: "these services MUST treat values specified by <param> and namelist identically" and "MUST also treat values specified by 'src' and <content> identically." The first pair shares one field (params) because both lower to the same Statifier.Machine.Param.t() shape and coerce through the same Statifier.EventData.coerce({:params, _}) ladder. The second pair cannot: src is a URI string the core never dereferences (ADR-0031 - whether anything downstream fetches it is a later concern), while content is markup or a value already resolved in hand, coerced through Statifier.EventData.coerce({:value, _}) or {:text, _}. One field cannot hold both a reference and a resolved value without a sentinel that collapses "no content, use src" with "content resolved to nil" - so 6.4's "treat identically" becomes two fields the reader of the effect payload treats identically, not one field that already has.

caller_context

caller_context is the opaque host term the current macrostep's triggering external event carried (ADR-0063, amended 2026-09-01), copied off Statifier.MachineState's transient slot at construction - nil when no context was attached. An invocation outlives the macrostep that started it exactly as a delayed send outlives the macrostep that scheduled it, which is the reopening trigger ADR-0063 named for a third durably-stored effect: a handler that starts work asynchronously (a durable job, a queue write) stores this term beside its own invocation row and restores it when the result comes back, so the result event links back to the trace that started the invocation - the parity with a durable timer's firing. The library never reads the value.

Summary

Types

t()

@type t() :: %Statifier.Effect.Invoke{
  autoforward: boolean() | nil,
  caller_context: term(),
  content: term(),
  invoke_id: String.t(),
  invoke_index: non_neg_integer(),
  macrostep: non_neg_integer(),
  microstep: non_neg_integer(),
  params: term(),
  round: non_neg_integer(),
  src: String.t() | nil,
  state_index: non_neg_integer(),
  type: String.t() | nil
}