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

The SCXML shapes the `core.*` vocabulary compiles to (ADR-0004 decisions
2-4), and the small builders the core types share.

## The one convention everything else rests on

Decision 2 fixes that a block compiles to exactly one state and signals
completion with `done.state.<state id>`. SCXML raises that event when a
**compound** state's configuration enters a `<final>` child, so every core
type emits a compound state carrying a `<final>` under the role `"done"`,
and arranges its own work to reach it. That is the whole of what a parent
needs: `StatifierBlocks.Compiler.Context` hands it a child's state id and
done event, never the child's SCXML.

Sequencing is therefore always the same shape - transitions **on the
parent's own state**, one per adjacent pair:

    <state id="s_SEQ" initial="s_c1">
      <transition event="done.state.s_c1" target="s_c2"/>
      <transition event="done.state.s_c2" target="s_SEQ__done"/>
      ...c1's subtree... ...c2's subtree...
      <final id="s_SEQ__done"/>
    </state>

A transition on a compound state fires while any descendant is active, so
the parent can wire its children without any of them knowing they were
wired. An empty run degenerates cleanly: `initial` points straight at the
`<final>`, and entering the block completes it.

## Interrupts: two regions and a two-event protocol

`core.group` and `core.resumable_group` have to run their body while
interrupt handlers watch for events. A handler is a block like any other
(decision 2 admits no exception), so it compiles to a state, and a state
only sees events while it is active - which means the handlers must run
**concurrently with the body**. The group therefore wraps both in a
`<parallel>`: one region for the body, one region per handler.

That leaves one problem. What a handler's arrival should *do* - abandon
the group, or resume it - is the handler's `outcome` config, and decision
4 deliberately keeps a child's config out of the parent's context. The
group cannot read it and must not try.

So the group wires **both** outcomes unconditionally and the handler picks
one by raising an event. A handler raises the bare pair, and the compiler
**salts** both halves with the group's own state id as it emits them
(ADR-0010 decision 8), so the group's own state carries:

    <transition event="statifier_blocks.interrupt.abandon.s_G" target="s_G__done"/>
    <transition event="statifier_blocks.interrupt.resume.s_G"  target="s_G__run"/>

and the `<raise>` a handler on that rail wrote as
`statifier_blocks.interrupt.resume` is emitted as
`statifier_blocks.interrupt.resume.s_G`.

That salt is what makes nesting reliable. Two nested groups used to carry
the *same* two transitions, and only the raise's position in the active
configuration decided which of them took it - which is right for a raise
from the inner group's own rail and wrong for a raise from the outer
group's rail while an inner group is active, where the outer group's
resume was captured by the inner group's rail and the outer group's
history re-entry never fired. After the salt each group's rail matches
only its own pair, so a rail can only be reached by a raise from that
group's own rail, at any nesting depth and whichever transition an engine
would otherwise have preferred.

Nothing an author or a host type writes moves: a host interrupt handler
still joins the protocol by raising the same two bare events, and
`interrupt_events/0` is still where they are named. The salt is applied at
emit and is invisible above it -
`StatifierBlocks.Compiler.Interrupts` rewrites the raises inside a rail,
and a raise outside any rail is left exactly as it was written.

`core.group` has nothing to remember, so its resume target is the
`<parallel>` itself and the body restarts. `core.resumable_group` targets
a `<history>` inside the body region instead, of the type its `history`
config names - which is the whole of what that config buys, and why
ADR-0002 decision 10 could leave it as one `:select` field.

# `chain`

```elixir
@spec chain([StatifierBlocks.Compiler.Context.child_summary()], String.t()) ::
  {String.t(), [StatifierBlocks.Emission.t()],
   [StatifierBlocks.Emission.node_t()]}
```

Sequences `summaries` and lands on `exit_target`.

Returns `{initial, transitions, child_refs}`: the state to enter first
(the first child, or `exit_target` for an empty run), one transition per
adjacent pair plus one from the last child to `exit_target`, and a
placeholder per child for the compiler to splice.

Each transition is attributed to **the child it leaves**, not to the
container that emitted it (ADR-0004 decision 5). "What happens after the
authorize step" is the fact an author would recognise, so a finding
against that transition belongs on the authorize block than on the sequence
around it. Attribution carries no bytes, so this changes nothing about
the generated chart.

# `final`

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

A `<final>`, the state whose entry raises the owning block's
`done.state`.

An id in the reserved `o_` namespace is an **outcome** final, so it also
raises that outcome's own completion event (ADR-0004's outcome
amendment, 2c):

    <final id="s_blk_AUTH__o_done">
      <onentry><raise event="done.outcome.s_blk_AUTH.done"/></onentry>
    </final>

Any other id stays a bare `<final>` - the `body_done` role `guarded/4`
mints is a completion marker inside the block, not an outcome a parent
can wire on.

The raise is what makes the summary honest. 2e's child summary
advertises `done.outcome.<state id>.<name>` for **every** child,
single-outcome ones included, and a summary naming an event nothing
raises would be a lie the compiler told its own parents. 2c makes the
raise constitutive of an outcome final, and `core.invoke` already
emitted exactly this shape by hand before this builder did.

The id is inverted back to `{state id, outcome}` by
`StatifierBlocks.Compiler.StateId.unoutcome_id/1`, which is exact
because a generated id contains exactly one `"__"` - a block id carries
none and a role is refused if it does.

# `interrupt_events`

```elixir
@spec interrupt_events() :: %{abandon: String.t(), resume: String.t()}
```

The two events an interrupt handler raises to tell the group it sits in
what to do. A host handler that wants to work inside `core.group` raises
one of these; a host group that wants to admit `core.on_event` transitions
on both.

# `interrupt_events`

```elixir
@spec interrupt_events(String.t()) :: %{abandon: String.t(), resume: String.t()}
```

The same two events, salted with the emitted state id of the group whose
rail they belong to (ADR-0010 decision 8).

This is the pair a group's own two transitions match, and the pair a
`<raise>` inside that group's rail is emitted as.
`StatifierBlocks.Compiler.Interrupts` reads it back to rewrite the raises,
so the salt's shape is written down once, here, rather than in each half
of the convention.

No two groups share an emitted state id
(`StatifierBlocks.Compiler.StateId` mints them), so no two rails share a
name.

# `interruptible`

```elixir
@spec interruptible(StatifierBlocks.Compiler.Context.t(), String.t() | nil) ::
  {:ok, StatifierBlocks.Emission.t()}
  | {:error, {:invalid_role, String.t(), String.t()}}
```

The interruptible shape: the body in one region, each handler in its own,
and the two-event protocol wired on the group's own state - salted with
that state's id, so this group's rail is the only rail its handlers can
reach (ADR-0010 decision 8; see the moduledoc).

`history` is `nil` for a group with nothing to remember, or `"shallow"` /
`"deep"` for one that resumes where it left off.

# `ordered`

```elixir
@spec ordered(StatifierBlocks.Compiler.Context.t(), [
  StatifierBlocks.Compiler.Context.child_summary()
]) ::
  {:ok, StatifierBlocks.Emission.t()}
```

The shape every plain ordered container emits: a compound state running
`summaries` in order and finishing at its own `<final>`.

# `state`

```elixir
@spec state(String.t(), String.t() | nil, [StatifierBlocks.Emission.node_t()]) ::
  StatifierBlocks.Emission.t()
```

A `<state>`; `initial` is dropped when `nil`, which is how an atomic state is written.

# `transition`

```elixir
@spec transition(
  keyword(),
  [StatifierBlocks.Emission.node_t()]
) :: StatifierBlocks.Emission.t()
```

A `<transition>`. `opts` carries `:event`, `:cond`, `:target` and
`:internal`; an absent one is simply not written, so an eventless
conditional transition and an unconditional one are the same builder.

`internal: true` writes `type="internal"`, and every transition a
container puts **on its own state targeting one of its descendants**
needs it. A transition is external by default, and an external
transition exits and re-enters its source even when the target is inside
it - which for a `<parallel>`'s region means tearing down the other
regions mid-flight. That is not a subtlety a block-type author should
have to rediscover, so the builders below set it and this note says why.

`cond_key` names the config field the `cond` came from verbatim, which
is what makes an upstream expression error the author's typo rather than
a bug in the block type (ADR-0004 decision 9). Pass it whenever the
condition is an author's `:expression` field rather than something the
type composed.

---

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