# `StatifierBlocks.Compiler.Context`
[🔗](https://github.com/riddler/statifier_blocks/blob/v0.44.1/lib/statifier_blocks/compiler/context.ex#L1)

What a block type is entitled to know while emitting, and nothing more
(ADR-0004 decision 4).

The compiler walks the document bottom-up and calls `emit/2` with the
children already compiled. The context it passes carries four things:

  * `block_id` and `state_id` - the block's own, precomputed;
  * `children` - slot name to the ordered list of child summaries, each
    carrying only `block_id`, `state_id` and `done_event`. A child's
    emitted SCXML is deliberately absent: a parent that could read it
    would be a parent that could depend on it, and decision 2 exists to
    prevent that;
  * `document_id` - for the rare type that names the document in a send
    target;
  * `role_id/2` - decision 3's minting function, reached through this
    struct so the compiler owns the namespacing.

The palette is deliberately absent. A block type resolving another block
type would be a block type compiling its own children, which is the
compiler's job, and would make `emit/2`'s purity depend on a value it did
not receive.

## The conventional `<final>` is the default outcome's final

Decision 2 makes every block's state compound with a `<final>` child, so
that entering the final raises `done.state.<state id>` and a parent needs
nothing but the child's id to wire it. Under ADR-0004's outcome
amendment that child is the **default outcome's** final: a block type
that declares no outcomes has exactly one, named `"done"`, so the
conventional final is `outcome_id(ctx, "done")` and lives in the
reserved `o_` namespace below rather than under an ordinary role.
`done_id/1` is its minting function and the core vocabulary uses it
uniformly. A block whose state can never reach a final is a block no
parent can sequence after.

## The `o_` role namespace is reserved

A block type with more than one way to finish emits one `<final>` per
outcome it reaches (ADR-0004's outcome amendment). Those finals live in
the role namespace under the prefix `"o_"`, they are minted only by
`outcome_id/2`, and `role_id/2` refuses the prefix outright - without the
reservation an outcome final and a hand-minted role could produce the
same id and provenance could not say which it was.

# `child_summary`

```elixir
@type child_summary() :: %{
  block_id: StatifierBlocks.Block.id(),
  state_id: StatifierBlocks.Compiler.StateId.t(),
  done_event: String.t(),
  outcomes: [outcome()]
}
```

Everything a parent may know about one compiled child: its block id, the
state it compiled to, the event that state raises when it is done, and
its declared outcomes.

`done_event` keeps its accepted meaning - `done.state.<state id>`, the
"finished, do not care how" signal - so a structural parent written
before outcomes existed still compiles and still behaves identically.
`outcomes` is in the type's declaration order, is always non-empty, and
holds exactly one entry, `done`, for a type that declared none.

# `outcome`

```elixir
@type outcome() :: %{
  name: String.t(),
  state_id: StatifierBlocks.Compiler.StateId.t(),
  done_event: String.t()
}
```

One of a child's declared outcomes: its name, the `<final>` its block
compiled that outcome to, and the event entering that final raises
(ADR-0004's outcome amendment, 2e).

# `t`

```elixir
@type t() :: %StatifierBlocks.Compiler.Context{
  block_id: StatifierBlocks.Block.id(),
  children: %{optional(StatifierBlocks.Block.slot_name()) =&gt; [child_summary()]},
  document_id: StatifierBlocks.Document.id(),
  state_id: StatifierBlocks.Compiler.StateId.t()
}
```

# `children`

```elixir
@spec children(t(), StatifierBlocks.Block.slot_name()) :: [child_summary()]
```

The ordered child summaries in `slot`, or `[]` for a slot with no
children - including one the document never wrote.

# `done_event`

```elixir
@spec done_event(t()) :: String.t()
```

The `done.state` event this block's state raises, for a block type that
needs to name its own completion.

# `done_id`

```elixir
@spec done_id(t()) :: StatifierBlocks.Compiler.StateId.t()
```

The id of this block's conventional `<final>` child - the **default
outcome's** final, `outcome_id(ctx, "done")` with the error arm
discharged, since `"done"` is a literal outcome name this module knows
is valid.

Under ADR-0004's outcome amendment (2b) that id is
`"s_" <> block_id <> "__o_done"`. It moved there from the ad-hoc role
`"done"` the core vocabulary used before, which is what makes the
amendment move compiled bytes for every document. The name and arity are
unchanged, so no block type had to be edited to follow it.

# `new`

```elixir
@spec new(StatifierBlocks.Block.id(), StatifierBlocks.Document.id(), %{
  optional(StatifierBlocks.Block.slot_name()) =&gt; [child_summary()]
}) :: t()
```

Builds the context for `block_id` under `document_id`, with `children`
keyed by slot name in the order `slots/1` declared them.

# `outcome_event`

```elixir
@spec outcome_event(t(), String.t()) ::
  {:ok, String.t()}
  | {:error, {:invalid_outcome, StatifierBlocks.Block.id(), String.t()}}
```

The completion event a parent wires on for one outcome:
`done.outcome.<state id>.<outcome>` (ADR-0004's outcome amendment, 2c).

The tag rides on an event rather than on the final's identity, because
`done.state.<state id>` is generated whichever final is entered and a
parent can therefore not see which one it was. A parent that does not
care wires the prefix `done.outcome.<state id>` and matches every outcome
of that child; one that discriminates names the full event. Refuses a
malformed outcome name for the same reason `outcome_id/2` does.

# `outcome_id`

```elixir
@spec outcome_id(t(), String.t()) ::
  {:ok, StatifierBlocks.Compiler.StateId.t()}
  | {:error, {:invalid_outcome, StatifierBlocks.Block.id(), String.t()}}
```

Mints the `<final>` id for one declared outcome (ADR-0004's outcome
amendment, 2b):

    outcome_id(block_id, outcome) = "s_" <> block_id <> "__o_" <> outcome

This is the **only** home for an outcome final's id. A block type with
more than one way to finish emits one `<final>` per outcome it reaches
and mints every one of them here rather than by string concatenation, for
decision 3's reason: the ids stay injective, `unstate_id/1` still inverts
them, and provenance can still say which block a final came from.

`outcome` is a name in the role shape - `~r/\A[a-z][a-z0-9_]*\z/`, no
`"__"` - and one that is not is refused with `{:error, {:invalid_outcome,
block_id, outcome}}` rather than raising.

> #### The tagged return is the ratified shape {: .info}
>
> 2e's original sketch wrote the signature as returning a bare
> `String.t()`. It cannot, and stay honest: 2f requires an
> `:invalid_outcome` Emit finding for a name failing the role shape, and
> decision 1 forbids `emit/2` raising, so the error has to be reachable
> through a return value. The `{:ok, _}` arm carries exactly the id 2e
> names. 2e was amended to this signature on 2026-08-29 (operator
> ruling); see its "Amended 2026-08-29" paragraph.

# `role_id`

```elixir
@spec role_id(t(), StatifierBlocks.Compiler.StateId.role()) ::
  {:ok, StatifierBlocks.Compiler.StateId.t()}
  | {:error,
     {:invalid_role, StatifierBlocks.Block.id(),
      StatifierBlocks.Compiler.StateId.role()}}
  | {:error,
     {:reserved_role, StatifierBlocks.Block.id(),
      StatifierBlocks.Compiler.StateId.role()}}
```

Mints the id of an auxiliary state this block emits **inside its own
state**, under `role` (decision 3).

Returns `{:error, {:invalid_role, block_id, role}}` for a role the
compiler could not invert; a block type that passes a literal role never
sees that arm, and one that builds a role from config handles it as the
ordinary Emit finding it is.

A role beginning with `"o_"` is refused with `{:error, {:reserved_role,
block_id, role}}`: that namespace belongs to outcome finals and
`outcome_id/2` is the only way into it.

# `summary`

```elixir
@spec summary(StatifierBlocks.Block.id()) :: child_summary()
```

The summary of one compiled child, for a parent building a summary of its
own or wiring a transition to a sibling.

`summary/1` is the ordinary case: a child whose type declares no
outcomes, which is the single default outcome named `"done"`. The
default lives in one place - this delegation - so a caller that does not
know a child's module still produces a summary of the same shape.

# `summary`

```elixir
@spec summary(StatifierBlocks.Block.id(), [String.t()]) :: child_summary()
```

The summary of one compiled child whose type declared `outcome_names`,
in declaration order (ADR-0004's outcome amendment, 2e).

Order is preserved and never sorted: it is decision 6's byte determinism
that depends on it. Every entry's `state_id` and `done_event` are
computed against the **child's** block id through `outcome_id/2` and
`outcome_event/2`, so a summary can name only ids and events the child
itself would mint.

A name that is not role-shaped is dropped rather than raising, which
keeps this function total. It is unreachable in the compiler: the Emit
stage refuses a malformed or duplicated outcome name with an
`:invalid_outcome` finding on the misbehaving block's own pass, before
any parent sees it in a summary.

---

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