# `StatifierBlocks.InvokeStep`
[🔗](https://github.com/riddler/statifier_blocks/blob/v0.44.1/lib/statifier_blocks/invoke_step.ex#L1)

The leaf step that **names** one host invoke type, as a base a host block
type declares itself out of (ADR-0007 decision 2).

A host block type that calls the host and waits for it to answer is three
facts - the invoke type it names, what its answer produces, and how the
palette draws it - wrapped in the same hundred lines every time. This
module is those hundred lines, written once: `use
StatifierBlocks.InvokeStep, invoke_type: "myapp:check_details"` declares
the behaviour and fills in every callback, and the type overrides only the
rows where it differs.

    defmodule MyApp.Blocks.CheckDetails do
      use StatifierBlocks.InvokeStep,
        invoke_type: "myapp:check_details",
        produces: "myapp.details_check",
        fields: [
          %{
            key: "assign_to",
            type: {:path, %{}},
            label: "Write the result to",
            required?: true,
            default: "registration.details_check"
          }
        ],
        palette: %{
          label: "Check details",
          group: "Registration",
          description: "Checks the visitor's details before the patron record is made.",
          icon: "identification",
          order: 0
        }
    end

## This module names invoke types; it never runs one

The two-registry seam ADR-0002 decision 2 draws is the thing easiest to
lose here, so it is worth saying in as many words: a block type names an
invoke type, and a handler the host registers separately - per session,
under st-ADR-0051 - is what runs it. Nothing in this module resolves a
name to a handler, and `use`-ing it registers nothing. A step whose
invoke type no handler answers is a deployment fact
`StatifierBlocks.Compiler.InvokeTypes` reports at the one moment a caller
holds both registries; it is not something this base can know.

## What it is not

It is not a block type. It implements no `StatifierBlocks.BlockType`
callback of its own, appears in no palette, and has no `type_name`: what
it holds is the decisions a leaf step makes the same way every time,
factored out so a host's twelve steps do not spell them twelve times.
`StatifierBlocks.Core.Invoke` remains the shipped `core.invoke` type and
is unaffected - see "The relationship to `core.invoke`" below.

## Every function here is pure

ADR-0002 decision 4's purity rule reaches anything a callback calls and
not only the callback itself, so it reaches this module in full: no
process dictionary, no application configuration, no IO, no clock, no
randomness. The injected callbacks are ordinary function definitions
over their arguments and the compile-time declaration, which is what
keeps a `use`-ing type as pure as a hand-written one.

## The relationship to `core.invoke`

`StatifierBlocks.Core.Invoke` is `core.invoke`: a step with an `on_error`
**slot**, so a failing call runs a subtree the author put there. This is
that emission with the slot taken out. A leaf step has no children, so
its failure path is an `error` outcome a parent may wire rather than a
subtree the block runs, and both outcome finals are emitted
unconditionally rather than only when a slot is occupied.

The two share the `invoke_type` grammar (through the internal config
helpers the `core.*` types share), the two event names, and the rule that
`assign_to` is written on the success transition rather than in a
`<finalize>` - the answer is only an answer when the call succeeded, and
`<finalize>` runs for every event the invocation delivers.

## What `use` injects

| Callback | Injected default |
|---|---|
| `current_version/0` | `1` |
| `slots/1` | `[]` - a leaf step has no children |
| `config_schema/1` | `label`, then `invoke_type`, then the `:fields` given |
| `validate_config/1` | the `invoke_type` and `assign_to` checks |
| `io/1` | `%{kinds: [:step]}`, plus `produces` when `:produces` is given |
| `outcomes/1` | `[{"done", "Done"}, {"error", "Error"}]` |
| `palette_entry/0` | the `:palette` given, over this module's defaults |
| `emit/2` | `emit/4` against the declared invoke type |
| `migrate_config/2` | `StatifierBlocks.BlockType`'s refusing default |

Every one is `defoverridable`. A type with extra `<param>` children calls
`emit/4` itself; one with a tighter rule adds to `validate_config/1`; one
that has changed its config shape overrides `current_version/0` and
`migrate_config/2` the way any block type does.

# `__using__`
*macro* 

Declares the behaviour and injects the leaf-step defaults (ADR-0007
decision 2). Options:

  * `:invoke_type` - **required**, the invoke type this step names when
    its config does not say otherwise. A literal in the
    `namespace:name` grammar is checked at compile time.
  * `:produces` - what a successful call produces to the next sibling
    (ADR-0003). Absent leaves the step unconstrained beyond being one.
  * `:fields` - extra field declarations, appended after `label` and
    `invoke_type` in the order given.
  * `:palette` - palette-entry keys, merged over this module's defaults.
  * `:failure_outcomes` - the outcome names this step's family counts as
    having failed, declared once here rather than as an `@impl` on every
    member. Absent, the injection is `failure_outcomes/0`'s `["error"]`,
    exactly as it was before the option existed. A host wrapper macro
    over this one may set it for its whole family (ADR-0002's Note of
    2026-09-07, item 6).

# `check_assign_to`

```elixir
@spec check_assign_to(
  [StatifierBlocks.BlockType.finding()],
  StatifierBlocks.Block.config()
) :: [
  StatifierBlocks.BlockType.finding()
]
```

Checks an `assign_to` the step declared as optional: a blank one is a
step that throws its answer away, which is an answer rather than a gap.

What it accepts is `core.assign`'s and `core.invoke`'s datamodel path -
any non-empty value with no whitespace, dotted or not (ADR-0011 decision
13, widened here on 2026-09-06). A step that requires the key instead -
because a decision nobody keeps is not a decision - declares the field
`required?: true` and adds a refusal of the blank of its own; this check
stays blank-permissive either way, so the two compose rather than
disagreeing about the same value. `check_identifier/4` is the shipped
shape for a step field whose rule really is a bare identifier, which
`assign_to` no longer is.

# `check_identifier`

```elixir
@spec check_identifier(
  [StatifierBlocks.BlockType.finding()],
  StatifierBlocks.Block.config(),
  String.t(),
  String.t()
) :: [StatifierBlocks.BlockType.finding()]
```

Checks that `key` holds a bare lowercase identifier, anchoring `message`
on that key when it does not.

# `check_invoke_type`

```elixir
@spec check_invoke_type(
  [StatifierBlocks.BlockType.finding()],
  StatifierBlocks.Block.config()
) :: [
  StatifierBlocks.BlockType.finding()
]
```

Checks a stored `invoke_type`, and passes an absent one - see
`invoke_type/2` for why absence is an answer rather than a gap.

# `config_schema`

```elixir
@spec config_schema(String.t(), [StatifierBlocks.BlockType.field_decl()]) :: [
  StatifierBlocks.BlockType.field_decl()
]
```

`label` first, then `invoke_type`, then whatever else the step declares.

`label` leads because it is the field an author reaches for first - it is
what the card says - and the inspector renders declaration order.

# `emit`

```elixir
@spec emit(
  StatifierBlocks.Block.t(),
  StatifierBlocks.Compiler.Context.t(),
  String.t(),
  [
    StatifierBlocks.Emission.t()
  ]
) ::
  {:ok, StatifierBlocks.Emission.t()}
  | {:error, StatifierBlocks.BlockType.emit_error()}
```

A compound state that calls the host and finishes at the `<final>` of
whichever outcome the call reached.

    <state id="s_blk_RCP" initial="s_blk_RCP__running">
      <state id="s_blk_RCP__running">
        <invoke type="myapp:receipt"/>
        <transition event="done.invoke" target="s_blk_RCP__o_done"/>
        <transition event="error.communication.invoke" target="s_blk_RCP__o_error"/>
      </state>
      <final id="s_blk_RCP__o_done">
        <onentry><raise event="done.outcome.s_blk_RCP.done"/></onentry>
      </final>
      <final id="s_blk_RCP__o_error">
        <onentry><raise event="done.outcome.s_blk_RCP.error"/></onentry>
      </final>
    </state>

Both transitions match by SCXML's descriptor prefix rule and neither
names an invocation id, which is safe for the reason `core.invoke`
gives: they sit on the inner state, active only while this block's own
call is outstanding, so no other invocation's completion can be selected
by them.

The `type` attribute is stamped as coming from `invoke_type` only when
the author actually wrote one. A step running on the declared default
has no config key for a finding to point at, and attributing the
attribute to a key the document does not carry would send an author
looking for text they never typed.

`params` are the `<param>` children the calling type wants on the
`<invoke>`, in the order it wants them - `literal_param/3` builds one. A
step with nothing to send omits the argument.

A config carrying `assign_to` puts an `<assign expr="_event.data">` on
the success transition, writing the handler's answer to that location. A
step that stores nothing there emits no `<assign>`, and an `assign_to`
that is not a bare identifier is a finding on the author's key rather
than an attribute nobody can read.

# `failure_outcomes`

```elixir
@spec failure_outcomes() :: [String.t()]
```

`error`, the outcome a step reaches when the call did not succeed
(ADR-0002's amendment of 2026-09-06, section 3).

The `use` macro defines `failure_outcomes/1` from this for every host
type built on it that does not declare `failure_outcomes:` itself, so a
step that calls out to the world and comes back on `error` is classed as
having failed without its author writing anything. It is in
`defoverridable`: a host whose `error` is routine - a probe that reports
"not found" through it, say - declares `failure_outcomes: []` at the
`use` site or defines its own `failure_outcomes/1`, in the same place it
would override `outcomes/1`.

This narrows one sentence of `c:StatifierBlocks.BlockType.failure_outcomes/1`'s
own Note: a type built on this module now exports the callback by
inheritance, so it is classed even though it was written before the
callback existed. The amendment counts that as a cost and takes it.

# `invoke_type`

```elixir
@spec invoke_type(StatifierBlocks.Block.config(), String.t()) :: String.t()
```

The invoke type this config names: what it stored, or the step's own
declared default when it stored nothing.

A step whose config omits the key is naming its own invoke type, which is
what the declared default says. The alternative is a document that cannot
say "the usual handler" without repeating it, and the field's default is
the one place the usual handler is written down.

# `invoke_type_field`

```elixir
@spec invoke_type_field(String.t()) :: StatifierBlocks.BlockType.field_decl()
```

The `invoke_type` field, declared with the step's own invoke type as its
default.

It is `required?: true` with a default for `core.on_event`'s reason: the
field is one an author must answer, and the answer is prefilled with the
only one that is usually right.

# `io`

```elixir
@spec io(StatifierBlocks.Assignability.produces() | nil) ::
  StatifierBlocks.Assignability.io()
```

A step constrains nothing beyond being a step unless it declares what a
successful call produces (ADR-0003).

# `label_field`

```elixir
@spec label_field() :: StatifierBlocks.BlockType.field_decl()
```

The optional `label` field: the name this particular step goes by on the
card, above the block type's own label.

Declaring it is the whole of what a host does to opt in - this package
titles a card from a declared `:string` field keyed `"label"` and demotes
the palette label to the subtitle (`StatifierBlocks.ViewModel.title/1`
and `subtitle/1`).

# `literal_param`

```elixir
@spec literal_param(String.t(), String.t(), String.t()) ::
  StatifierBlocks.Emission.t()
```

A `<param>` carrying a literal, for a value the block type stores rather
than reads out of the datamodel.

`config_key` is stamped on the emission as the provenance of the value,
so a finding inside it points at the author's field and not at this
module.

# `outcomes`

```elixir
@spec outcomes() :: [StatifierBlocks.BlockType.outcome_decl()]
```

The two ways a call can finish, in the order they compile in.

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

# `palette_entry`

```elixir
@spec palette_entry(StatifierBlocks.BlockType.palette_entry()) ::
  StatifierBlocks.BlockType.palette_entry()
```

A palette entry with this base's shared presentation defaults filled in.

`attrs` wins over every default, so a step that says something is
believed.

# `verdict`

```elixir
@spec verdict([StatifierBlocks.BlockType.finding()]) ::
  :ok | {:error, [StatifierBlocks.BlockType.finding()]}
```

Turns an accumulated finding list into `validate_config/1`'s return,
restoring the order the checks ran in.

---

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