The one dispatch point every executable-content node implements. There is
no central case/cond anywhere in this tree that switches on a node's
kind (docs/architecture.md:101-105's named v1 mistake); a node's runtime
behavior lives in exactly one place, this protocol's defimpl for that
node's struct, in the same file as the struct itself
(Statifier.Machine.Content.Raise, Statifier.Machine.Content.Log,
Statifier.Machine.Content.If, Statifier.Machine.Content.Foreach). A
future datamodel element (<script>) or session element
(<send>/<cancel>/<invoke>) adds a struct and a defimpl, never a
clause here or in the block runner.
Statifier.Machine.Content.If is the first composite node - one that
runs child content of its own rather than acting alone - and its own
moduledoc is where the reasoning for folding its own children directly,
rather than calling back into the block runner, lives.
Statifier.Machine.Content.Foreach is the second composite node and
reuses that reasoning unchanged; its own contribution is settling what
<if> left open only by analogy - spec 4.6.3 states outright that a
child failure halts "the block that contains it" too, so the three-element
{:error, context, reason} form's purpose is spelled
out in the spec text itself here, not inferred.
Contract
execute/2 takes the compiled node and a
Statifier.ExecutableContent.Context.t() and returns:
{:ok, context, [Statifier.Effect.t()]}- the node ran.contextis the (possibly updated) context threadingmachine_stateforward;effectsare the ones this node produced, in the order it produced them. Most nodes emit zero or one effect, but the shape does not assume that.{:error, reason}- the node failed.reasonis a bareterm(); an implementation never constructs aStatifier.Eventand never raises - turning a failure intoerror.executionis the block runner's job, not a leaf's (ADR-0003's error model: "only the interpreter raiseserror.execution"). This keeps the errors-are-events conversion in exactly one place regardless of how many node kinds exist.{:error, context, reason}- the node failed, and state it had already legitimately produced (queued events, datamodel writes,pending_errors) must not be discarded with it. A node returns this form when it has already produced state that must not be discarded with the failure - which is every composite node (one that runs child content), and, per ADR-0047,<send>: a leaf that has minted a send id and writtenidlocationbefore rejecting an invalid target or unsupported type. Every other leaf still returns the two-element form.
Both error forms keep the same rule: an implementation never constructs a
Statifier.Event and never raises.
Effect ordering
Effects come back from execute/2 in the order the node itself produced
them; the runner accumulates each node's effects onto the block's list in
the same document order it executed the nodes, so the final list a caller
sees is execution order, start to finish, never re-sorted.
No @fallback_to_any
A node struct reachable from a c_index that has no execute/2
implementation is a compiler bug - Machine-is-valid-by-construction
(docs/architecture.md:34-37) means such a struct should never exist at
runtime. Leaving the fallback off turns that bug into a Protocol.UndefinedError
naming the offending struct, which is a better diagnosis than any
fallback behavior could be.
Summary
Types
What execute/2 returns: success threads the context forward with any effects produced, in order; failure carries an opaque reason for the runner to convert, either bare (a leaf) or alongside the context a composite node had already produced before it failed.
All the types that implement this protocol.
Functions
Runs node against context, returning result(). See the moduledoc for
the full contract: what the node may and may not do, and what each half of
the return value means.
Types
@type result() :: {:ok, Statifier.ExecutableContent.Context.t(), [Statifier.Effect.t()]} | {:error, term()} | {:error, Statifier.ExecutableContent.Context.t(), term()}
What execute/2 returns: success threads the context forward with any effects produced, in order; failure carries an opaque reason for the runner to convert, either bare (a leaf) or alongside the context a composite node had already produced before it failed.
@type t() :: term()
All the types that implement this protocol.
Functions
@spec execute(node :: t(), context :: Statifier.ExecutableContent.Context.t()) :: result()
Runs node against context, returning result(). See the moduledoc for
the full contract: what the node may and may not do, and what each half of
the return value means.