# `StatifierBlocks.Core.Invoke`
[🔗](https://github.com/riddler/statifier_blocks/blob/v0.44.1/lib/statifier_blocks/core/invoke.ex#L1)

`core.invoke`: a step that calls the host and waits for it to answer,
with an optional subtree for the failure case (ADR-0002 decision 10, as
amended 2026-08-29 section D).

This type **names** an invoke type; it never runs one. Which handler a
name resolves to is deployment state supplied per session (st-ADR-0051),
and the palette is authoring state supplied per operation - the
two-registry seam ADR-0002 decision 2 draws, which this is the first
`core.*` type to stand on. `StatifierBlocks.Compiler.InvokeTypes` is what
a host compares the two against, at the one moment it holds both.

## The failure path is a slot, not a port

A failing call is one of the two ways this block can finish, and the way
an author says what happens then is by putting blocks in the `on_error`
slot. It is not a second outlet with an author-drawn edge: every edge in
a document is a parent/slot/child relationship, which is the invariant
the editor's rendered connectors rest on (ADR-0002's amendment
section A2). The slot is a rail beside the step, declared with
`zero_or_one` arity and the `:failure` slot style ADR-0005 decision 10's
2026-08-29 amendment (10g) names for exactly this slot: an in-band
continuation path taken on a bad outcome, rendered on the rail
`core.group`'s `interrupts` already uses but in the error family rather
than the interrupt one. The rendering half of 10h is the renderer's, not
this type's: all a block type does is declare the style.

## Two outcomes

`done` and `error`, in that order. Under ADR-0004's outcome amendment
each one the block reaches compiles to its own `<final>`, minted through
`StatifierBlocks.Compiler.Context.outcome_id/2`, whose entry raises
`done.outcome.<state id>.<outcome>`. A parent that does not care which
way the call went wires the prefix and never learns an outcome name; one
that does names the full event. **The block decides nothing about what
comes next**: its emission ends at the final it enters.

## `assign_to` names a datamodel path

The field is declared `{:path, %{}}` - ADR-0002 decision 7's eighth field
type - so the editor offers the host's declared datamodel paths as
candidates on it, a value the datamodel does not declare draws ADR-0005
clause 11e's `:info` advisory rather than a refusal, and the write this
block makes is visible to `StatifierBlocks.Environment` as `:unknown` at
that path (ADR-0011 decision 2) instead of being emitted where nothing
declared can see it. Its rule is `core.assign`'s and `core.subchart`'s -
any non-empty path with no whitespace - for ADR-0011 decision 13's
reason: one `<assign>` element writing one datamodel cannot have two
rules about what a location may be. A blank `assign_to` is a call whose
answer is thrown away, which is an answer rather than a gap.

## The params field is knowingly provisional

ADR-0002 decision 7's field types are a closed set and none of them is "a
list of name/path pairs", so `params` is a `:string` holding one
`name=path` per line. That is the amendment's own compromise, recorded in
its section D1 as a deferred question: whichever way it is resolved - a
new field type, a dedicated control, or the flattening as shipped - it is
a decision 7 change rather than a change to this type's row.

# `emit`

A compound state that runs the call in an inner state and finishes at
the `<final>` of whichever outcome it reached.

    <state id="s_INV" initial="s_INV__running">
      <state id="s_INV__running">
        <invoke type="myapp:authorize">
          <param expr="order.amount" name="amount"/>
        </invoke>
        <transition event="done.invoke" target="s_INV__o_done">
          <assign expr="_event.data" location="authorization"/>
        </transition>
        <transition event="error.communication.invoke" target="s_blk_PARK"/>
      </state>
      {the on_error child's own subtree}
      <transition event="done.state.s_blk_PARK" target="s_INV__o_error" type="internal"/>
      <final id="s_INV__o_done">
        <onentry><raise event="done.outcome.s_INV.done"/></onentry>
      </final>
      <final id="s_INV__o_error">
        <onentry><raise event="done.outcome.s_INV.error"/></onentry>
      </final>
    </state>

## Both transitions match by prefix, and neither names an id

The `<invoke>` carries no `id`, so the engine mints one, and the two
transitions match `done.invoke` and `error.communication.invoke` by
SCXML's descriptor prefix rule rather than naming the invocation. That
is safe for exactly the reason ADR-0004's worked example gives: both
transitions sit on the inner state, which is active only while this
block's own call is outstanding, so no other invocation's completion can
be selected by them.

`error.communication.invoke.<invoke_id>` is statifier-ex ADR-0068's
name - a blessed suffix extension of the `error.communication` that
st-ADR-0051 decision 1 already assigns to this failure - which is what
makes the prefix match here and a host chart's existing
`error.communication` handler both correct at once.

## An absent `on_error` still ends the block on `error`

ADR-0002's amendment of 2026-09-06, section 2: the failure final is
emitted whether or not the slot is occupied, because a class is read off
a final - the only thing that raises
`done.outcome.<state id>.error` is that final's own `onentry`, and an
outcome whose final is sometimes absent cannot be classed. With the slot
empty the failure transition targets the final directly; with it
occupied every byte is what it was, the transition targeting the child
and the child's own `done_event` carrying it into the final.

An author who wants the old silence for a particular call has the slot:
an occupied `on_error` is the amendment's section 4 definition of
handling, and a handled failure reaches nothing above the block.

## Who owns what

Everything here is this block's except one transition: the one leaving
the `on_error` child for the error final is attributed to **that child**,
because "what happens after the parking step finishes" is a fact about
the child (decision 5, the same rule `Emit.chain/2` follows). The
`type` attribute's value is stamped as coming from `invoke_type` and the
`location`'s from `assign_to`, so an upstream finding inside either is
the author's typo rather than a bug in this type.

# `failure_outcomes`

`error` is failure-classed: a call that came back on the error path did
not succeed (ADR-0002's amendment of 2026-09-06, section 3).

That is what the outcome has always meant and what the `:failure`
`slot_style` on `on_error` has always drawn. The `on_error` slot is
unchanged by the class - amendment section A2's refusal to marry an
outcome to a slot stands, and the slot and the outcome share a word here
the way `core.subchart` and `core.map` have had both for longer.

# `io`

A step with two outcomes, so `produces` is `:unknown` for the reason
`core.branch` declares it: joining the type this block's own call
produces with the type its `on_error` subtree produces is the lattice
ADR-0003 decision 4 refuses to build.

`consumes` is absent - an invoke reads its inputs through `params`, out
of the datamodel, rather than through the type flow.

# `outcomes`

`done` and `error`, in that order - the pair this type has always
emitted (ADR-0002's amendment of 2026-09-06, section 1).

Fixed rather than config-derived, and the same pair
`StatifierBlocks.InvokeStep.outcomes/0` returns for every host type
built on it. Until that amendment this type exported no `outcomes/1` at
all, so `StatifierBlocks.BlockType.outcomes/2` answered the default
single `done` while `emit/2` minted an `error` outcome final beside it -
a raised event in no summary, which ADR-0004's outcome amendment forbids
in its clause 2e.

Declaration order is read by ADR-0004 decision 6's byte determinism, so
this list is never sorted.

# `param_rows`

```elixir
@spec param_rows(term()) :: {:ok, [{String.t(), String.t()}]} | {:error, String.t()}
```

The `params` field's rows, as `{name, path}` pairs in the order the
author wrote them.

One `name=path` per line, blank lines ignored. Public because the editor
and the tests both need the same reading of the flattened field, and two
spellings of it would be two chances for them to disagree - the field is
provisional (ADR-0002's amendment, D1) and this function is where the
provisionality is contained.

# `slots`

One optional slot, `on_error`, holding what runs when the call fails.

Its arity is `zero_or_one` because a failure path is one continuation,
not a list of them: an author who wants several steps there puts a
`core.sequence` in it, exactly as anywhere else a single child is asked
for.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
