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

A palette names the block types a host makes available: a map from a
block's `type_name` to the entry implementing `StatifierBlocks.BlockType`
for it (ADR-0002 decision 2).

An entry is a `t:type_ref/0` - a module, or a module paired with an
opaque term it carries (ADR-0002's 2026-09-07 amendment). One type name
still resolves to one entry, and nothing in this package calls a callback
on an entry except through `call/4`, the one call seam. That is what lets
a host whose *users* save block types register a declaration held as
data: `StatifierBlocks.Composite.Data` is such an entry, and no caller of
a callback can tell which kind it got.

It is a **caller-supplied value**, nothing more. A palette is built once
for an editing or compiling operation and passed explicitly into whatever
needs it - document validation, the editor's session state, the compiler -
the same way any other value is threaded through a pipeline. Nothing in
this package holds a palette across operations, and no cadence beyond "one
value per operation" is implied: a caller that wants the same set of types
for its next operation builds (or reuses) the value again, deliberately.

It is explicitly **not**:

  * an application-configuration lookup keyed by block type
  * a table of shared entries reachable by name from anywhere in the
    process tree
  * a lookup registered under a well-known process name
  * anything wired up automatically when this package (or a host
    application) starts

Any of those would make two hosts sharing one runtime step on each
other's block types - the multi-tenant property this design exists to
keep. Two `StatifierBlocks.Palette` values built with different modules
under the same `type_name` in the same running system resolve
independently; neither can see or clobber the other.

Every consumer that walks a document and resolves blocks against a
palette carries the case where a block's `type_name` has no entry as an
ordinary pattern-matched arm, not as an exception - `fetch/2` never
raises, so there is nothing to rescue.

## Recipes, the second map

A palette also names **recipes** (ADR-0005 clause 1C): arrangements an
author picks the way they pick a block type, implemented by modules
behind `StatifierBlocks.Recipe`. Everything above about what a palette
*is* applies to the second map unchanged - it is a caller-supplied value,
there is no global registry, and two hosts in one runtime resolve
independently.

The names live in one namespace **per map**, not one across both. A
recipe named `"deadline"` and a block type named `"deadline"` do not
collide, because nothing resolves a name without knowing which map it is
asking: a document's `type_name` is looked up in `types` and only there,
and a palette browser entry carries which of the two it came from.

## Validators, the list

A palette also carries **validators** (ADR-0005 clause `11p`): modules
behind `StatifierBlocks.DocumentValidator`, each stating one of the host's
own whole-document rules. They ride the palette for the reason
`assignability` and `recipes` do - it is already the value a host builds
and hands in, so a host declaring a rule adds a module to a value it was
building anyway, with no assign, no mount option and no editor callback
added.

They are a **list**, not a third map. Nothing resolves a validator by
name, so there is nothing to key on and nothing to collide: every module
in the list runs, in list order.

## Ordering a group

A `palette_entry/0` declares a `group` and an `order`, and ADR-0005
decision 10 sorts a browser group by `order`. Two entries of one group
declaring the same number have no answer - the pick between them is
whatever the sort happened to do - so `new/2` refuses that palette, and
`from_modules/2` refuses it because it builds through `new/2`.

The numbering convention this package follows, and the one a host
extending a group is asked to follow:

  * **One number, one entry - with one exception.** A composite's `types`
    entry and that same composite's derived `<Module>.Recipe` declare the
    same card by construction, and a host may register both
    (`StatifierBlocks.Composite`, "The derived recipe"). That pair is
    admitted; any other two entries at one number are refused.
  * **Numbers are append-only.** A new entry takes the next number after
    the highest one its group already uses. Existing numbers are not
    renumbered to open a hole in the middle, because an author picks a
    block by where it sits and a renumber moves every entry below it.
  * **A group is dense, and that is a consequence, not a rule.** The core
    `"Structure"` group runs 0 through 17 with no gap: the seventeen
    `core_types/0` entries and the `"deadline"` recipe, which took 17 as
    the last one added. There is no free interior number, so the next
    core entry takes 18.
  * **A host merging into a core group numbers above the core's last.**
    `new(Map.merge(core_types(), %{...}))` puts a host's entry into the
    same `"Structure"` group, where the core numbers are already taken;
    a host that wants its own numbering from 0 declares its own `group`
    instead, which is a separate namespace for ordering as much as the
    browser's headings.

None of this is enforced beyond the collision refusal: `order` is an
optional key, a gap is legal, and an entry that declares no `order` at
all is skipped rather than refused.

# `manifest_entry`

```elixir
@type manifest_entry() ::
  {StatifierBlocks.Block.type_name(), pos_integer()} | {recipe_name(), :recipe}
```

One line of a palette's manifest: a block type's name beside its module's
`current_version/0`, or a recipe's name beside `:recipe`.

A recipe carries the marker rather than a number because a recipe has no
version to carry - `StatifierBlocks.Recipe` declares no `current_version/0`,
and a placeholder integer would read as one.

# `preflight_finding`

```elixir
@type preflight_finding() ::
  %{
    type: StatifierBlocks.Block.type_name(),
    block_id: StatifierBlocks.Block.id() | nil,
    key: String.t(),
    declared_type: StatifierBlocks.BlockType.field_type(),
    value: StatifierBlocks.Block.json(),
    message: String.t()
  }
  | %{
      type: StatifierBlocks.Block.type_name(),
      block_id: StatifierBlocks.Block.id() | nil,
      raised: :config_schema | :new_block | :resolve,
      message: String.t()
    }
```

One finding of `preflight/1` or `preflight/2`: a plain map a host can
match on. Every finding carries the block type's name under `:type`, the
block's id under `:block_id` (`nil` when the finding is about the type
itself rather than a block of a document), and an author-facing
`:message`. The rest says which of two things it is:

  * a **field type the value disagrees with**: `:key` is the field's key,
    `:declared_type` its declared field type as `config_schema/1` spells
    it, and `:value` the value its declared type does not admit. The
    `:message` is the binding check's own, the one a compile of that
    config reports under the field;
  * a **callback that raised**: `:raised` names the step the pre-flight
    was taking - `:config_schema` (reading the type's declarations, for
    its defaults or for a document block's config), `:new_block`
    (building a fresh block of it with `new_block/2`) or `:resolve`
    (resolving a document's block with `resolve/2`, which may migrate
    it) - and the `:message` is the raise's banner.

# `recipe_name`

```elixir
@type recipe_name() :: String.t()
```

A recipe's name, as the palette browser and a pick name it.

# `registration`

```elixir
@type registration() :: {StatifierBlocks.Block.type_name(), type_ref()}
```

One registration: the name a document uses, and the entry implementing
it. ADR-0002 decision 1 puts the string in the document and the mapping
in the palette, so a registration carries both halves - see
`from_modules/2` for why the name is not derived from the module.

The second element is a `t:type_ref/0`, so a host registers a stateful
entry in the ordered list it already writes.

# `t`

```elixir
@type t() :: %StatifierBlocks.Palette{
  assignability: module() | nil,
  recipes: %{optional(recipe_name()) =&gt; module()},
  types: %{optional(StatifierBlocks.Block.type_name()) =&gt; type_ref()},
  validators: [module()]
}
```

# `type_ref`

```elixir
@type type_ref() :: module() | {module(), state :: term()}
```

What one `types` entry may be: a module, or a module paired with an
opaque term carried beside it (ADR-0002's 2026-09-07 amendment).

One type name still resolves to one entry. `state` is opaque to this
module - it is *carried* and *prepended*, and nothing here fixes its
shape. `StatifierBlocks.Composite.Data` is the one module in this package
that declares a shape for its own, and a host's stateful type declares
its own.

There is no `:kind` key on an entry, and a pair is not one: it is one
entry that carries a term. Every callback on an entry goes through
`call/4`, which is where the pair stops being visible.

# `call`

```elixir
@spec call(type_ref(), atom(), [term()], term()) :: term()
```

Calls `callback` on a palette entry, answering `default` when the entry
does not declare it.

This is the **one call seam**. Nothing in this package calls a callback
on a module a palette resolved by writing `module.callback(...)`; it
writes `Palette.call(ref, :callback, args, default)`, and three things
that would otherwise be repeated at every site live here instead:

  * **the arity arithmetic** - a callback declared at arity *n* is
    exported at arity *n + 1* by a stateful entry's module, and asking
    about the wrong arity would silently answer "not declared" and
    degrade a stateful type into the absent-callback path, which looks
    exactly like a type that declared nothing;
  * **the absent-callback default**, which is why this takes four
    arguments and not three: ten of the fifteen callbacks are optional
    and every site is a probe followed by a fallback, so folding the
    probe into the seam is what makes the arity arithmetic unrepeatable;
  * **the `state`-prepending**, which is the whole of what a caller must
    not know.

For a bare `module` entry it calls `module.callback(args...)`; for a
`{module, state}` entry it calls `module.callback(state, args...)`.

For one of the five **required** callbacks the default is unreachable -
a module that does not export `emit/2` is not a block type at all - and a
caller passes a value whose appearance would be a bug rather than a
degradation. The seam does not distinguish the two cases; the behaviour's
required list already does.

It does **not** rescue on behalf of a site that does not rescue today, it
does not memoize, and it changes no callback's declared arity in
`StatifierBlocks.BlockType`. A rescue is B3's degradation, about a
callback that *raises*; the seam is about how an entry is *reached*.

    iex> alias StatifierBlocks.Palette
    iex> Palette.call(StatifierBlocks.Core.Send, :current_version, [], nil)
    1

    iex> alias StatifierBlocks.Palette
    iex> Palette.call(StatifierBlocks.Core.Sequence, :sentence, [%{}], :absent)
    :absent

    iex> alias StatifierBlocks.Palette
    iex> Palette.call(NoSuchModule, :current_version, [], :absent)
    :absent

# `core`

```elixir
@spec core() :: t()
```

The `core.*` structural vocabulary as a palette (ADR-0002 decision 10).

The core vocabulary's entries, described in `StatifierBlocks.Core`.
They are ordinary palette entries with no privileged path anywhere in
this package - a palette without them is as valid as a palette with
them, and a host that wants only some of them builds a map with only
those.

    Palette.core()
    #=> %StatifierBlocks.Palette{types: %{"core.sequence" => ..., ...}}

# `core_recipes`

```elixir
@spec core_recipes() :: %{optional(recipe_name()) =&gt; module()}
```

The `name => module` map of core recipes, beside `core_types/0`
(ADR-0005 clause 4C).

One entry, `"deadline"`: the `core.send` and `core.on_event` pair
ADR-0010 decision 1 spells, as one palette pick. A palette built without
it is as valid as a palette with it, which is the property `core_types/0`
already has - nothing in this package has a privileged path to a recipe
either.

    iex> Map.keys(StatifierBlocks.Palette.core_recipes())
    ["deadline"]

# `core_types`

```elixir
@spec core_types() :: %{optional(StatifierBlocks.Block.type_name()) =&gt; module()}
```

The `type_name => module` map behind `core/0`, for a host merging the
core vocabulary with its own entries:

    Palette.new(Map.merge(Palette.core_types(), %{"myapp.authorize" => MyApp.Blocks.Authorize}))

A host entry sharing a name with a core one wins, because that is what
`Map.merge/2` does and a palette is just a value: nothing in this package
reserves the `core.` prefix, and a host deliberately swapping in its own
`core.wait` is doing something this design allows on purpose.

# `declares?`

```elixir
@spec declares?(type_ref(), atom(), arity()) :: boolean()
```

Whether the entry declares `callback` at the declared arity `arity`,
without calling it.

`arity` is the arity `StatifierBlocks.BlockType` declares, not the one a
stateful entry's module exports: this function does the same arithmetic
`call/4` does, so a caller never writes `arity + 1`.

It exists for the two sites that need **declaredness alone** rather than
a value - `sentence/1` feeding ADR-0005's three-way chain, and
`outcomes/1` deciding whether a card draws an outcome row at all. Both
feed a *presentation* branch rather than a fallback value, so neither can
be expressed as `call/4` with a default. It is one predicate, not a
second seam: `call/4` is written in terms of it.

    iex> alias StatifierBlocks.Palette
    iex> Palette.declares?(StatifierBlocks.Core.Send, :sentence, 1)
    true

    iex> alias StatifierBlocks.Palette
    iex> Palette.declares?(StatifierBlocks.Core.Sequence, :sentence, 1)
    false

# `fetch`

```elixir
@spec fetch(t(), StatifierBlocks.Block.type_name()) ::
  {:ok, type_ref()}
  | {:error, {:unknown_block_type, StatifierBlocks.Block.type_name()}}
```

Resolves a `type_name` to its entry. Total; never raises (ADR-0002
decision 3). `Map.fetch/2` rather than a sentinel default, so a palette
that genuinely maps a name to `nil` stays distinguishable from a name no
entry carries.

It answers the entry **as stored**: it neither normalizes a bare module
into `{module, nil}` nor unwraps a pair into its module. That is what
keeps this source-compatible for every host that has a palette today - a
host matching `{:ok, module}` still matches, because a host that
registered no stateful entry can be handed no pair. Call a callback on
what comes back through `call/4`, never directly.

# `fetch_recipe`

```elixir
@spec fetch_recipe(t(), recipe_name()) ::
  {:ok, module()} | {:error, {:unknown_recipe, recipe_name()}}
```

Resolves a recipe name to its module. Total; never raises, for `fetch/2`'s
reason.

The second map only. A name that is a block type and not a recipe answers
`{:error, {:unknown_recipe, name}}`, which is clause 1C's two-namespace
rule as a function: nothing resolves a name without knowing which map it
is asking.

    iex> StatifierBlocks.Palette.fetch_recipe(StatifierBlocks.Palette.core(), "deadline")
    {:ok, StatifierBlocks.Core.DeadlineRecipe}

    iex> StatifierBlocks.Palette.fetch_recipe(StatifierBlocks.Palette.core(), "core.send")
    {:error, {:unknown_recipe, "core.send"}}

# `from_modules`

```elixir
@spec from_modules(
  [registration()],
  keyword()
) :: t()
```

Builds a palette from an **ordered, explicit list** of registrations -
the shape a host uses to contribute its own block types.

    Palette.from_modules(
      [
        {"myapp.risk_hold", MyApp.Blocks.RiskHold},
        {"myapp.settle", MyApp.Blocks.Settle}
      ],
      core: true
    )

This is `new/2` with the ergonomics the registration story actually
wants, and nothing more: it is still a value, built where the editor is
mounted and handed in explicitly. There is no global registry, no
application-configuration lookup, and no compile- or boot-time discovery
of modules implementing the behaviour - every reason the moduledoc gives
for that applies here unchanged, and a discovery pass would additionally
make two tenants in one runtime share whatever the code path happened to
find.

Options:

  * `:core` - when `true`, the registrations sit on top of
    `core_types/0` rather than on an empty map, and the recipe
    registrations sit on top of `core_recipes/0`. Defaults to `false`, so
    `from_modules([])` is the empty palette.
  * `:recipes` - an ordered list of `{name, module}` recipe registrations
    (clause 1C), read the same way and with the same "later entries win"
    rule. A host registering its own recipe under a core recipe's name
    reads its own, because it wrote it later.
  * `:assignability` - passed through to `new/2`.

The list is ordered and **later entries win**, which is what makes it a
list rather than a map: a host that deliberately swaps in its own
`core.wait` writes it after `core: true` and reads the override in the
order it happens, and the same name appearing twice in one list has an
answer rather than a coin flip.

## Why a name per entry, and not a name per module

A bare `[module]` list would be shorter, and this function does not take
one, because nothing in `StatifierBlocks.BlockType` declares a type
name. ADR-0002 decision 1 is explicit that the document names a type by
string and the palette resolves the string - the mapping is the *host's*
fact, not the module's, which is exactly what lets one module serve two
names in two tenants' palettes. Deriving a name from a module would
need a declaration the accepted behaviour does not have, and adding one
is a change to that record rather than an implementation convenience.

## What it checks, and what it does not

It does not assert the behaviour, and it does not resolve a module it
cannot load: a palette is a value that may name a module compiled later,
and every consumer already carries the unresolvable case as an ordinary
arm (ADR-0002 decision 3).

It refuses two things, both mount-time programmer errors with no sensible
degraded reading, each raising `ArgumentError` rather than quietly
building a palette a host would misread:

  * a registration that is neither a `{type_name, module}` nor a
    `{type_name, {module, state}}` pair - the message names the offending
    entry. This one is `from_modules/2`'s own, because a map has no
    registrations to malform;
  * two entries of one palette-browser **group** declaring the same
    `order` - the message names both, by name and module. That one is
    `new/2`'s, and reaches here because this function builds its palette
    with `new/2`: both builders refuse the same collision the same way.

# `manifest`

```elixir
@spec manifest(t()) :: [manifest_entry()]
```

The palette as a sorted list of `{name, version}` entries - the one value
a host pins to assert what its palette carries.

    Palette.manifest(Palette.core())
    #=> [{"core.assign", 1}, {"core.await", 1}, ..., {"deadline", :recipe}]

Types and recipes share one sorted list, and the second element says which
map an entry came from: an integer is a block type at that
`current_version/0`, `:recipe` is a recipe. The two names are still two
namespaces (see the moduledoc), so a palette carrying a type and a recipe
both named `"deadline"` produces both entries; sorted, the type comes
first.

It is a **list rather than a hash or a count**, because the point is the
failure message. A count moves from 27 to 29 and says nothing about which
types arrived; a hash says only that something moved. A list diffs
entry by entry, so the assertion that fails names the type that was added,
removed, or version-bumped.

Sorting is what makes two palettes comparable: `new/2` takes a map and
`from_modules/2` a list, so insertion order is not a fact about a palette
and the manifest does not carry one.

This function calls into the modules a palette names - `current_version/0`
on each, the same call `resolve/2` makes - so a palette naming a module
that is not compiled raises here, where `fetch/2` would not.
(`new/2`'s duplicate-`order` check also calls a module, but only one it
can already load, and it skips the rest rather than raising.)

# `new`

```elixir
@spec new(
  %{optional(StatifierBlocks.Block.type_name()) =&gt; type_ref()},
  keyword()
) :: t()
```

Builds a palette from a `type_name => module` map. Defaults to an empty
palette.

Options:

  * `:assignability` - a module implementing
    `StatifierBlocks.Assignability.Relation` (ADR-0003 decision 6).
    Defaults to `nil`, meaning the palette declares no widening relation -
    `new(types)` and `new(types, assignability: nil)` are the same palette.
  * `:recipes` - a `name => module` map of `StatifierBlocks.Recipe`
    implementations (clause 1C). Defaults to `%{}`. It is a second map
    rather than a second kind of entry in the first, because the two names
    are two namespaces.
  * `:validators` - a list of `StatifierBlocks.DocumentValidator`
    implementations, the host's own whole-document rules (ADR-0005 clause
    `11p`). Defaults to `[]`, and a palette that declares none pays
    nothing: there are no modules to call and the view model is identical.
    It is a **list rather than a map**, and ordered - there is no name to
    key on, nothing resolves a validator by name, every module in it runs
    in list order, and a later entry does not replace an earlier one. That
    is deliberately not `types`/`recipes`' rule, because those are lookups
    and this is not.

It refuses one thing, and raises `ArgumentError` rather than building a
palette a host would misread: two entries of one palette-browser
**group** declaring the same `order` (see "Ordering a group" in the
moduledoc). Types and recipes are checked together, because the browser
draws them into one group. An entry whose module is not loaded, exports
no `palette_entry/0`, or declares no `order` is skipped, not refused, and
a composite beside its own derived recipe is admitted - that pair is one
declaration read twice, not two entries disagreeing about a number.

The check lives here rather than in `from_modules/2` because a host that
merges maps - the shape `core_types/0` documents - never goes through
`from_modules/2`, and the collision is the same mount-time programmer
error whichever builder produced it.

# `new_block`

```elixir
@spec new_block(t(), StatifierBlocks.Block.type_name()) ::
  {:ok, StatifierBlocks.Block.t()} | :error
```

Builds an unconfigured block of `type_name` from this palette: the type's
`c:StatifierBlocks.BlockType.config_schema/1` defaults as the config, and
its `c:StatifierBlocks.BlockType.current_version/0` as the stored
`type_version`.

This is what "insert this type" means as a value, with no editor in it.
It lives beside `fetch/2` because it is the same question one step
further on - a name resolved to a module, then that module asked what a
fresh block of it looks like - and a host view that inserts from a
palette should not have to reimplement the answer.

Total, for `fetch/2`'s reason: a `type_name` no entry carries is `:error`
rather than a raise. The schema defaults are the *only* source of config
here. A palette entry's `:default_config` is a separate declaration and
is deliberately not merged in (that is the editor's insert probe, and
whether an inserted block should start from it is an open question, not
a settled one).

    iex> {:ok, block} =
    ...>   StatifierBlocks.Palette.new_block(StatifierBlocks.Palette.core(), "core.send")
    iex> block.type
    "core.send"
    iex> block.config
    %{"delay" => "", "event" => ""}
    iex> block.type_version == StatifierBlocks.Core.Send.current_version()
    true

    iex> StatifierBlocks.Palette.new_block(StatifierBlocks.Palette.core(), "no.such.type")
    :error

# `preflight`

```elixir
@spec preflight(t()) :: [preflight_finding()]
```

Lists every host type in `palette` whose own defaults disagree with its
declared field types: the question to ask before taking the minor that
makes a declared field type binding (ADR-0002 decision 7, amended
2026-09-28). An empty list means the palette is ready.

A **host type** is every entry except this package's own core module
under its core name (`core_types/0`), so `Palette.core()` answers `[]`; a
host's own module registered under a `core.*` name is a host type. For
each one, in type-name order, it judges through the same binding check
the compiler, the edit gate and the view model run:

  * each field's `default` in `config_schema(%{})`, against the
    declaration that carries it, and
  * the config of the block `new_block/2` builds for the type.

The two usually find one value; a value is listed once. Every rule past
a field's type stays the type's own `validate_config/1`, which the
pre-flight does not call. See `t:preflight_finding/0` for the shape.

Pure over `palette`, and it never raises on a type: a type whose
`config_schema/1` or `new_block/2` raises is a finding of its own, and
the rest of the palette is still judged.

    iex> StatifierBlocks.Palette.preflight(StatifierBlocks.Palette.core())
    []

# `preflight`

```elixir
@spec preflight(t(), [StatifierBlocks.Document.t()]) :: [preflight_finding()]
```

`preflight/1`, then every block of `documents` whose type `palette`
carries, judged as a compile judges it: resolved through `resolve/2`
first (so an older stored block is judged as migrated in memory), then
its config through the binding check. Each such finding names the block
under `:block_id`. Hand it the documents a host already stores to find
the configs the binding minor will refuse.

A block whose type `palette` does not carry is not judged, and nor is a
block `resolve/2` refuses: that refusal is already `resolve/2`'s. Core
types' blocks are judged like any other, since the binding holds for
them too. Findings come in `documents` order, each document's blocks in
`StatifierBlocks.Document.blocks/1` order. Pure over its arguments; a
raising callback is a finding, never a raise.

# `resolve`

```elixir
@spec resolve(t(), StatifierBlocks.Block.t()) ::
  {:ok, type_ref(), StatifierBlocks.Block.t()}
  | {:error, {:unknown_block_type, StatifierBlocks.Block.type_name()}}
  | {:error, {:block_type_too_new, StatifierBlocks.Block.id(), pos_integer()}}
  | {:error, {:migration_failed, StatifierBlocks.Block.id(), term()}}
```

Resolves `block` through `palette` and, if needed, migrates its config in
memory (ADR-0002 decision 8).

Four distinguishable outcomes, checked in this order:

  * the block's `type` has no entry in `palette` ->
    `{:error, {:unknown_block_type, type}}`
  * `block.type_version == module.current_version()` -> `{:ok, module,
    block}`, `block` returned exactly as given
  * `block.type_version > module.current_version()` -> `{:error,
    {:block_type_too_new, block.id, block.type_version}}`. Hard error,
    never a best-effort read: the code is older than the data, and
    guessing is how a rollback corrupts documents
  * `block.type_version < module.current_version()` -> `module.migrate_config/2`
    is called once, straight from the stored version to current (never a
    version-by-version ladder). A successful migration rewrites only
    `block.config` on the returned struct; a failing one, or a module that
    does not export `migrate_config/2` at all, becomes `{:error,
    {:migration_failed, block.id, reason}}`

The migrated config is applied to the returned struct only - `resolve/2`
never calls `Document.to_json/1`, `from_json/1`, or anything else that
could persist. Persisting a migration is the caller's decision. The
returned block's `type_version` is left **as stored**, never bumped to
`current_version()`, so the result can never be mistaken for a block that
was migrated on disk.

`resolve/2` takes one block, not a document - it never walks a document;
the caller owns the walk and what to do with a per-block failure.

---

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