Upgrading from 2.5 to 2.15.0: what a host changes

Copy Markdown View Source

This page is for a host: the application that compiles charts, starts executions and supplies the services a chart reaches through <invoke> and <send>. For each release from 2.6.0 to 2.15.0 it says what a host must change to take the release, and then what a host may start doing with it. Where a host must change nothing, the page says NONE. The Changelog says what the library changed; this page does not repeat it.

Of the releases in this range, only 2.14.0 has a Breaking entry in the CHANGELOG. Every "must change" from 2.6.0 to 2.10.0 is NONE. 2.11.0 changes some answers, and its "must change" names the hosts each one reaches: a host whose charts carry a <send> <param> with both expr and location, a host that matched an error shape or rescued a raise the release replaces with a named refusal, and a receiver that parsed a list or map parameter's text. 2.12.0 changes some answers too, and its "must change" names the hosts each one reaches: a receiver of a Basic HTTP send whose <content expr> evaluates to a struct, a host whose charts carry an initial on a compound state with no id naming a target outside that state, a host that reads the entries of a document already refused for a shared id, a host that holds predicator at 9.0 to 9.3, and a chart with a \u or \U escape in a string literal. 2.12.1 changes documentation only, and its "must change" is NONE. 2.13.0 changes some answers, and its "must change" names the hosts each one reaches: a host whose charts carry an initial on a <state> with no id that names a missing state or sits on a state with no children, a host that reads the entries of a document already refused for a shared id, and a host that waited for a further halted message after reporting a budget-halted session's failed send. 2.14.0 changes some answers, and its "must change" names the hosts each one reaches: a host whose documents write one attribute twice on one element, or that matches the validator's reasons exhaustively (the Breaking entry); a host that cancels a budget-halted session with events queued; and a host that waited for a further halted message after a delayed send fired into a budget-halted session. 2.15.0 changes one answer, and its "must change" names the hosts it reaches: a host whose charts call a function from cond or expr that is not In, a predicator builtin or a function the host declares, and a host that matches the compiler's error reasons exhaustively. 2.15.0's CHANGELOG lists that refusal under Changed, not as a Breaking entry: it refuses only a chart that calls a function no evaluation context holds, a call that raised error.execution whenever it was evaluated. A dependency requirement of {:statifier, "~> 2.5"} already accepts every version on this page. Raise it only when your host calls a function a later release added: each section names the lowest requirement its functions need.

2.6.0

A host must change: NONE. A session started without :send_types behaves as it did in 2.5: a <send> whose type is not built in still raises error.execution.

A host may start: handing <send> types to its own processors (ADR-0069). Requires {:statifier, "~> 2.6"}.

  1. Write a module per type that implements Statifier.Send.Processor: deliver/3 and cancel/2 are required and pure; the optional perform/2 does the delivery, and the optional ioprocessors_entry/1 fills the type's _ioprocessors entry. perform/2 may be called more than once for the same send, so make it idempotent. A delayed send is your processor's timer: the session schedules nothing for it.
  2. Pass the map to Statifier.Session.start_link/2 as send_types: %{"my-type" => MyProcessor}, and add inherit_send_types: true if the children a chart starts through <invoke> should get the same map. A map that names a built-in type ("scxml", the SCXML Event I/O Processor URI, or nil) makes the session refuse to start with {:error, {:send_types, {:built_in_types, types}}}.
  3. When your processor cannot deliver a send and the sender still exists, call Statifier.Session.failed_send/3; the chart then sees error.communication with the send's id. When the sender has finished, that call writes nothing and recording the miss is yours. A host without a session process makes the same write with Statifier.Interpreter.deliver_internal/5.
  4. At publish time, refuse a chart that names a type you never registered: Statifier.Send.Types.unsupported_sends/2, called with the compiled chart and Statifier.Send.Types.from_send_types/1 of the map you will start it with, lists each such <send> with its location.
  5. A resumed session does not remember which processor holds a delayed send. Routing a <cancel> for a send handed over before the position was saved is yours, as re-arming timers after a resume already is.

The guide is the section "The <send> half: host-registered send types" of How to extend Statifier: <invoke> handlers and <send> types.

2.6.1

A host must change: NONE. The release adds conformance cases under conformance/, which is not in the Hex package.

A host may start: nothing new. A sibling implementation that vendors the corpus re-vendors it at the v2.6.1 tag; that is not a host step.

2.7.0

A host must change: NONE.

A host may start: checking a chart's events at publish time (ADR-0071). Requires {:statifier, "~> 2.7"}.

  • Statifier.Chart.events/1 gives a compiled chart's event vocabulary: every event descriptor on a transition from a state some path can enter, as authored.
  • Statifier.Chart.check_accepts/2 compares a declared list of the event names the chart accepts with that vocabulary and answers %{unreachable: [...], undeclared: [...]}. unreachable holds the declared names the chart can never select on; undeclared holds the descriptors the declaration leaves out. With nil for the declaration both lists are empty. To ask whether one event name reaches the chart, call check_accepts(machine, [name]) and read unreachable.

Both functions report and refuse nothing: which list refuses a publish is your decision. Publish-time checks: every runtime refusal and its twin lists the runtime refusal each one stands in front of.

2.8.0

A host must change: NONE.

A host may start: comparing two compiled charts before publishing the second, and checking whether one execution's position survives the edit (ADR-0072). Requires {:statifier, "~> 2.8"}.

  • Statifier.Chart.diff/3 takes the published chart, the candidate and an optional mapping: of renamed state ids (old id to new id), and answers %{class: class, reasons: reasons} with a class of :identical, :compatible, :mapped or :breaking. It raises ArgumentError on an option other than mapping: or an invalid mapping.
  • Statifier.Position.compatible_at?/3 takes the two charts and one execution's Statifier.Position.export/1 map and answers true only when that execution's position is untouched by the edit. Both charts must carry their source, which Statifier.compile/2 keeps; a chart built without it answers false.

Neither function moves an execution, and the library calls neither. Whether an execution moves to the new chart, and when, stays your decision.

2.8.1

A host must change: NONE.

An <invoke> whose type is http://www.w3.org/TR/scxml, the SCXML type URI without its trailing slash, now starts an SCXML child session; before 2.8.1 it raised error.execution. A handler your host registered under :invoke_handlers for that exact string still receives it, since a registered handler takes precedence over the built-in one. If a chart of yours carries that spelling and your host relied on the error.execution, that chart now starts a child instead. A host that needs the new behaviour requires {:statifier, "~> 2.8 and >= 2.8.1"}.

A host may start: nothing new. The corpus changes follow 2.6.1's rule: a sibling implementation re-vendors at the v2.8.1 tag.

2.9.0

A host must change: NONE. Statifier.MachineState gains a last_selection field, which a position blob does not carry, so a position saved under 2.8.1 restores as before.

A host may start: running every publish-time check in one call. Requires {:statifier, "~> 2.9"}.

  • Statifier.Publish.findings/2 takes a compiled chart and a declaration of the host's send_types:, invoke_types: and accepts:, and answers a list of findings, each naming its row of Publish-time checks: every runtime refusal and its twin with a kind, a location and its data. Like 2.7.0's functions, it refuses nothing: which finding refuses a publish is your decision.
  • Statifier.MachineState's last_selection reads :selected when the last external event selected a transition, :none when it selected none, and nil before any external event.

2.10.0

A host must change: NONE. A session that registers no send type sees nothing new: no request is made, nothing is started, and _ioprocessors holds the SCXML processor's entry alone. A {:statifier, "~> 2.9"} requirement already accepts 2.10.0.

A host may start: the W3C Basic HTTP Event I/O Processor (ADR-0075). Requires {:statifier, "~> 2.10"}.

  • Register {Statifier.Send.BasicHTTP, base_url: base} in :send_types under both of its type strings, "http://www.w3.org/TR/scxml/#BasicHTTPEventProcessor" and "basichttp". Each string's _ioprocessors entry holds one "location": the base URL, /, and the session's _sessionid. A registration without :base_url refuses the session's start with an ArgumentError. The POSTs go through OTP's :httpc by default, and a :transport option names your own Statifier.Send.BasicHTTP.Transport; the package adds no dependency.
  • The processor receives nothing on its own: your front answers at the base URL and hands each request to Statifier.Send.BasicHTTP.decode/1, which answers the event to enqueue or the reason to refuse the request.
  • Every POST carries the send's deduplication key in an scxml-send-key header. A front that enqueues a request only when it has not already enqueued one with the same value delivers each send once; one that ignores the header sees at-least-once delivery.
  • For a processor of your own, a :send_types value may now be {module, opts}: the options reach deliver/3 and cancel/2 under the plan context's :opts key. A processor may implement the optional ioprocessors_entry/2, which receives the type string and a context carrying the session id and the options, so its entry can address one session. A bare-module registration behaves as it did in 2.9.0.

The guide is The Basic HTTP Event I/O Processor. The conformance corpus claims the processor's W3C documents and its case schema gains host.event_io_processors; a sibling implementation re-vendors the corpus at the v2.10.0 tag, which is not a host step.

2.11.0

A host must change: only where one of these reaches it. A session that registers no send type sees nothing new, and a {:statifier, "~> 2.10"} requirement already accepts 2.11.0.

  • A chart whose <send> carries a <param> with both expr and location no longer compiles: Statifier.Validator.validate/2, and so Statifier.compile/2, refuses it as {:param_expr_and_location, name}. Before 2.11.0 it compiled and the parameter took location. Drop one of the two attributes; keeping location keeps what the chart sent. A <send> <param> with neither attribute is refused as {:param_no_value, name}, where Statifier.compile/2 raised FunctionClauseError.
  • A fresh Statifier.Session.start_link/2 whose Statifier.Send.BasicHTTP registration has no usable string :base_url answers {:error, {:send_types, {:invalid_registration, type, {:missing_option, :base_url}}}}, refused before any session process is spawned and so with no crash report. It answered {:error, {%ArgumentError{}, stacktrace}}. If your host matched the ArgumentError shape, match the named refusal instead. Every registration the processor can build its entry from still starts. A resume is not refused and does not change: its position carries the _ioprocessors entries it started with. ioprocessors_entry/2 still raises ArgumentError for a direct caller. Corrected 2026-10-04: the named refusal is decided before the session process starts, so a start that also had another fault answers the named refusal too, where the start answered that other fault: a :name already registered ({:already_started, pid}), an :invoke_handlers value that is not a map ({:function_clause, stacktrace}), or another processor's _ioprocessors entry that is not a map (that processor's ArgumentError). Beside another processor whose check_registration/2 breaks its contract (it raises, throws, exits or answers anything other than :ok or {:error, reason}), the registration is not refused by name and the start answers the ArgumentError shape as before. ADR-0069's Note of 2026-10-04 records this.
  • Statifier.Lowering.lower/2, and so Statifier.compile/2, answers {:error, [%Statifier.Lowering.Error{reason: {:unexpected_root, name}}]} for a document whose root is any SCXML element other than <scxml> (a <state>, a <parallel>, a <final>, a <history>, a <transition>, ...), where it raised BadMapError, and {:unsupported_element, "scxml"} for an <scxml> nested below the root, where it raised FunctionClauseError. If your host rescued either raise, match the error tuple instead.
  • Statifier.Validator.validate/2 no longer raises FunctionClauseError on a <history> whose compound parent has no id: a default target outside the parent is reported as {:initial_not_descendant, target, nil}. If your host matches that reason's parent id as a binary, handle nil too.
  • Statifier.Send.BasicHTTP writes a parameter value that is a list or a map, and a <content> body that is a list, as JSON text (["Dune",2], {"title":"Dune"}), where it wrote the value's inspect/1 text; an undefined value inside such a list or map is written as JSON null. A list or map holding anything with no JSON form (a key that is not a string, a struct such as a Date, a tuple, a string that is not UTF-8) keeps its whole inspect/1 text, and every scalar keeps its text. A <content> body that is a list is still sent as text/plain. If a receiver of your POSTs parsed the inspect/1 text, read JSON instead. The inbound decoder is unchanged.

Three changes to a Statifier.Send.BasicHTTP delayed send ask nothing of a host, but a chart sees them. A delayed send whose transport raises now reaches the sender as error.communication, through Statifier.Session.failed_send/3 with the reason {:raised, exception}; before, nothing reached the chart. A delayed send of a live session that is busy when the delay passes is now POSTed; before, it was dropped. A cancel the send's timer has received before its POST now always wins. An immediate send whose transport raises is unchanged.

A host may start: refreshing a session's registered _ioprocessors entries, refusing a registration in its own processor, and telling a processor's own process that its session halted. Requires {:statifier, "~> 2.11"}.

  • Statifier.MachineState.refresh_ioprocessors/1 recomputes a position's registered _ioprocessors entries from the registration it is stamped with, and answers {:ok, machine_state} or a registration's {:error, reason} with nothing changed; an entry that raises raises here. A host whose Basic HTTP base URL moved across a resume re-stamps the position with Statifier.MachineState.put_send_types/2, refreshes it, and then passes it as :resume. Statifier.Session.refresh_ioprocessors/1 refreshes a live session and answers :ok, or, changing nothing, {:error, reason} for a registration its processor rejects ({:error, {:missing_option, :base_url}} for Basic HTTP without :base_url), {:error, :not_running} once the session has halted, {:error, :recorded_session} for a session started with record: true, or {:error, {:ioprocessors_entry, type, exception}} when an entry raises, the session running on at the position it held. The SCXML entry and an entry from a processor exporting only ioprocessors_entry/1 are untouched, and a resume still reads the entries its position carries. A session that registers nothing answers success and sees nothing change. ADR-0075's Amendments of 2026-10-02 record both calls.
  • A processor of yours may implement the optional Statifier.Send.Processor callback check_registration/2, which answers :ok or {:error, reason} for a registration's type string and options. A fresh start asks it for every registration whose module exports it and refuses the first one rejected with {:error, {:send_types, {:invalid_registration, type, reason}}}, before any session process is spawned; a resume does not ask it. A processor that does not implement it is not asked. ADR-0069's Amendment of 2026-10-02 records the callback. Corrected 2026-10-04: Statifier.Send.BasicHTTP implements the callback, so 2.11.0 also adds the public function Statifier.Send.BasicHTTP.check_registration/2, which a host may call to check a Basic HTTP registration's options before a start. It answers :ok exactly when ioprocessors_entry/2 would build an entry from the options (a string :base_url), and {:error, {:missing_option, :base_url}} for any other registration, a :base_url that is present but not a string included.
  • A processor that holds a delay in a process it starts from perform/2 can hand that process to Statifier.Session.HaltNotice.watch/2; the session sends it {:statifier_halted, session, reason} when it halts (:done, :cancelled or :budget_exhausted), at once when it already has. HaltNotice.take/1 removes and returns the processes watched under a key. Outside a session watch/2 keeps nothing and answers :not_a_session. The delay stays the processor's (ADR-0069 decision 4).

The conformance corpus changes too. The W3C Basic HTTP documents test518, test519, test520 and test534 now check the decoded event their transition takes, not its name alone, and a statifier case's step may carry an optional expect_position, the position the chart holds after that step (ADR-0076), with three new library cases. The corpus hash in conformance/manifest.json moves, so a sibling implementation that vendors the corpus re-vendors it at the v2.11.0 tag, which is not a host step.

2.12.0

A host must change: only where one of these reaches it.

  • Statifier.Send.BasicHTTP sends a <content expr> that evaluates to a struct as the body, its inspect/1 text as text/plain. Before 2.12.0 such a content took the form arm. A struct that does not enumerate as parameter pairs (a Date, for example) made planning the send raise (Protocol.UndefinedError for a Date), the session performing it exited, and no request was made. A struct that does enumerate as pairs (a MapSet of two-element tuples, for example) was sent as an application/x-www-form-urlencoded body of those pairs; a receiver of such a send now gets the struct's inspect/1 text as text/plain instead, the text it already reads for a struct inside a list or a map. A session that registers no send type sees nothing new.
  • Statifier.Validator.validate/2, and so Statifier.compile/2, refuses a chart whose compound <state> has no id and carries an initial attribute or an <initial> element naming a target outside that state: it reports {:initial_not_descendant, target, nil}. Before 2.12.0 such a chart validated and Statifier.compile/2 raised KeyError. A target inside the state is accepted as before. If your host rescued the raise, match the refusal instead, and if it matches that reason's parent id as a binary, handle nil too, as 2.11.0 asked for a <history> parent.
  • Statifier.Validator.validate/2, and so Statifier.compile/2, decides whether a <history> default target sits under the history's parent by the document's structure for every parent, as it already did for a parent with no id. A document where two states share an id and a history under one of them names a target under the other was already refused for that id ({:duplicate_id, id}, or {:empty_id} for an empty one); it now also reports {:initial_not_descendant, target, parent_id}. No document that validated before is refused, and none is accepted that was refused.
  • statifier requires {:predicator, "~> 9.4"}, where it required ~> 9.0. A host that holds predicator at 9.0 to 9.3, in its own mix.exs or in its mix.lock, moves to 9.4 with it (mix deps.update predicator). With it, a string literal holding a non-ASCII character in an expression reaches _event.data and a Statifier.Send.BasicHTTP text body as the UTF-8 string written. Before 2.12.0 each such character became one byte, the low eight bits of its code point, so a text body carrying one was not the literal and was usually not valid UTF-8.
  • With the same move, a string literal holding a \u or \U escape no longer reads as its letters with the backslash dropped. Before 2.12.0 'caf\u00e9' read as cafu00e9; write the character itself instead. What a chart sees depends on where the literal sits. In an expression the compiler checks at load (a transition's cond, a <log expr> or a <send> <content expr>, for example), Statifier.compile/2 refuses the document with an :expression_compile_error. In a <data expr>, an <assign expr>, a <script> or a namelist entry, where the compiler defers the failure, it raises error.execution when it runs, and Statifier.Publish.findings/2 reports it under row S13. Wherever text is read as a value, through Statifier.Compiler.Expressions.inline_value/1 or Statifier.EventData.coerce/1, it no longer folds to a string and is kept as its text, quotes and backslash included: the text of a <data> or an <assign> element; a <content> element's text under <send>, <invoke> or <donedata>; an inbound Basic HTTP request's text body or form parameter values.

A host may start: nothing new.

The conformance corpus changes too. conformance/schema/case.json now refuses a step's expect_position whose history_values gives a history state anything but an array of unique, non-empty state ids, and the library case loan_lost_after_timer states the position after the step that stops its chart: running false, an empty configuration and lost still in states_to_invoke. The corpus hash in conformance/manifest.json moves, so a sibling implementation that vendors the corpus re-vendors it at the v2.12.0 tag, which is not a host step.

The conformance runner compares a position's numbers by JSON value: a float the chart holds agrees with an expected integer of the same value (2.0 with 2), where before it disagreed and its message named no member, and a number that differs in value still disagrees, naming the member. A sibling implementation's runner compares the same way; this is not a host step either.

2.12.1

A host must change: NONE. The release changes documentation only: no file under lib/ differs from 2.12.0.

A host may start: nothing new. The conformance corpus does not change, so a sibling implementation that vendored it at the v2.12.0 tag has nothing to re-vendor.

2.13.0

A host must change: only where one of these reaches it. A {:statifier, "~> 2.12"} requirement already accepts 2.13.0.

  • Statifier.Validator.validate/2, and so Statifier.compile/2, refuses two documents whose <state> has no id: one whose initial names a missing state, reported as {:unresolved_initial, missing_id}, and one with no child states that carries an initial, reported as {:initial_on_atomic_state, nil}. Before 2.13.0 both validated and Statifier.compile/2 raised KeyError. If your host rescued the raise, match the refusal instead, and if it matches initial_on_atomic_state's id as a binary, handle nil too, as 2.12.0 asked for initial_not_descendant's parent id.
  • Statifier.Validator.validate/2, and so Statifier.compile/2, decides whether a <state>'s initial target is its descendant by the document's structure for every state, as 2.12.0 began doing for a <history> default target. A document where two states share an id and one's initial names a state under the other was already refused for that id ({:duplicate_id, id}); it now also reports {:initial_not_descendant, target, id}. No document that validated before is refused, and none is accepted that was refused.
  • A session halted :budget_exhausted that is handed a miss through Statifier.Session.failed_send/3 drops it without stepping, as a session halted :done or :cancelled does. Before 2.13.0 it stepped the halted chart again and sent its subscribers another {:halted, :budget_exhausted} for every miss. A host that waited for that further message after reporting a miss no longer receives it; the miss is the host's dead letter. A session halted :done or :cancelled is unchanged.

Statifier.Testing.HandlerCase's check that a handler exception propagates now waits up to ten seconds for the session's exit instead of one, so a host's generated handler test no longer fails on a loaded machine; a passing run returns as soon as the exit arrives. This is not a host step.

A host may start: replacing a running session's registration for one send type. Requires {:statifier, "~> 2.13"}.

  • Statifier.Session.replace_send_type/3 takes a session, a send type the session already registers and a new registration (a module or {module, opts}, as a :send_types value of start_link/2 is), and recomputes that type's _ioprocessors entry in the same call. A host whose front moved a registration's options while the session runs (a new :base_url for Statifier.Send.BasicHTTP, or a rotated location) hands the session the new registration, the chart reads the new entry from its next event, and later sends of the type go through the new registration. It answers :ok, or changes nothing and answers {:error, :not_running} for a halted session, {:error, :recorded_session} for a session started with record: true, {:error, {:unknown_send_type, type}} for a type the session does not register, {:error, {:send_types, {:invalid_registration, type, reason}}} when the processor's check_registration/2 refuses the registration (as start_link/2 spells that refusal), and {:error, {:ioprocessors_entry, type, exception}} when the entry raises, the session keeping its position. ADR-0069's Amendment of 2026-10-09 records the call.

The conformance corpus's hash does not move: no case changes. The case schema's descriptions and the corpus README now say that a step's expected datamodel keeps _name for a document whose <scxml> carries a name attribute, and that the corpus writes each array of state ids sorted while the schema does not check the order; no verdict changes.

2.14.0

A host must change: only where one of these reaches it. A {:statifier, "~> 2.13"} requirement already accepts 2.14.0.

  • Breaking: Statifier.Validator.validate/3, and so Statifier.compile/2, refuses a document that writes one attribute twice on one element, with the error reason {:duplicate_attribute, element, attribute} (code :duplicate_attribute), located at the repeated occurrence. Before 2.14.0 such a document compiled using the first occurrence's value. XML does not allow the repeat: remove it from the document. If your host matches Statifier.Validator.Error's reasons exhaustively, add a clause for the new reason.
  • Statifier.Session.cancel/1 sent to a session halted :budget_exhausted with events queued (by Statifier.Session.send_event/2, or by a delayed send to the session itself fired after the halt) reaches it and halts it :cancelled, discarding the queued events, so {:halted, :cancelled} follows the budget halt as the session's documentation promises. Before 2.14.0 the cancel stayed queued behind those events and the session stayed :budget_exhausted.
  • A delayed send fired while a session is halted :budget_exhausted, to an internal target (a targetexpr evaluating to #_internal) or to a route unreachable at fire time, is dropped without stepping: the core is not driven, nothing is recorded, and no further message reaches a subscriber, as a budget-halted sender's miss reported through Statifier.Session.failed_send/3 is dropped since 2.13.0. Before 2.14.0 it drove the halted core again and sent another {:halted, :budget_exhausted}. A host that waited for that further message no longer receives it. A fired send to the session itself is still enqueued on its inbox, and one to another live session is still delivered to it.

A session halted :done or :cancelled is unchanged by either session change.

A host may start: nothing new.

The conformance corpus changes too. The statifier suite gains two library cases, loan_renew_refused_by_standing and loan_dispute_restores_late_shallowly, and conformance/registry.json claims both. The corpus hash in conformance/manifest.json moves, so a sibling implementation that vendors the corpus re-vendors it at the v2.14.0 tag, which is not a host step. No case repeats an attribute on one element, so the new refusal moves no case. The case schema's descriptions now say that a step's expect_position members are compared as JSON values, and that the corpus writes active_invocations sorted while the schema does not check the order; no verdict changes.

2.15.0

A host must change: only where this reaches it. A {:statifier, "~> 2.14"} requirement already accepts 2.15.0.

  • Statifier.compile/2 refuses a chart whose expressions call a function that is not In, a predicator builtin or a function the host declares, with a Statifier.Compiler.Error whose reason is {:unknown_function, name, source}, located at the element that holds the call. Before 2.15.0 such a chart compiled, and every evaluation of the call raised error.execution. Register the function with :function_providers, or declare its name with :function_names. If your host matches Statifier.Compiler.Error's reasons exhaustively, add a clause for the new reason. This includes a chart a host stored before 2.15.0 and recompiles under it, from its source or from a Statifier.Chart blob.

A host may start: registering named functions a chart may call from cond and expr (ADR-0078). Requires {:statifier, "~> 2.15"}.

  1. Write a Predicator.FunctionProvider module per family of functions. A function reads only its arguments and the host term: it does no I/O, reads no clock and no random source, and keeps no state, so a replay or a resume evaluates it the same.
  2. Pass the list as :function_providers to Statifier.compile/2, Statifier.MachineState.new/2 and Statifier.Session.start_link/2. A provider may not replace In or a predicator builtin. A list that does not resolve makes Statifier.Session.start_link/2 refuse with {:error, {:function_providers, reason}} before it starts anything, and makes Statifier.compile/2 and Statifier.MachineState.new/2 raise ArgumentError.
  3. A tool that checks a chart without loading the modules passes :function_names, the names alone, to Statifier.compile/2.
  4. A session started with :resume stamps the list it is given, and a recording keeps it for Statifier.Replay. A host that resumes a position itself re-stamps the list with Statifier.MachineState.put_function_providers/2.
  5. A child session started for an <invoke> does not inherit the list, and a chart given as <invoke> content cannot call a host function.

The guide is the section "Host functions for cond and expr" of How to extend Statifier: <invoke> handlers, <send> types and host functions.

The conformance corpus does not change in 2.15.0.