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

The group-scoped interrupt pair: a `<raise>` of the interrupt protocol
emitted inside a group's rail is rewritten to that group's own salted
event name (ADR-0010 decision 8).

## The defect this closes

The protocol is two strings - `StatifierBlocks.Core.Emit`'s
`interrupt_events/0` - and before decision 8 every interruptible group
emitted the same two transitions matching them. One name at every nesting
depth means the rails of two nested groups listen for the same event, and
only the raise's position in the active configuration decides which of
them takes it. That is right for a raise from the inner group's own rail
and wrong for a raise from the *outer* group's rail while a second railed
group is active in the outer group's body: the inner rail's source sits
deeper, so it takes the outer group's resume and the outer group's
history re-entry - the whole point of `core.resumable_group` - never
fires.

`StatifierBlocks.Core.Emit.interruptible/2` emits the group's own two
transitions already salted with its state id. This pass is the other
half: the raises inside the rail, which the group cannot reach when it
emits, because a parent never receives its children's SCXML (ADR-0004
decision 4) - what it emits in a handler's place is a `{:child, block id}`
placeholder. By the time the compiler has the children's compiled
emissions in hand, it also has the parent's own emission saying where
each of them sits, and that is exactly what this pass reads.

## Which raises are a rail's, structurally

Nothing here names a block type, in the same spirit as
`StatifierBlocks.Compiler.Cancels`. The parent's own emission is walked
for a `<state id="X">` that carries a transition on
`Emit.interrupt_events(X)` - a state matching its own salted pair is a
group's rail-bearing state, and `X` is the salt. Its rail children are
the `{:child, _}` placeholders sitting directly in the `<parallel>` under
it: the body is a `<state>` region the group emitted, so a body child's
placeholder is nested inside that state and is never one of these. A host
group type that builds its rail through `Emit.interruptible/2` gets the
scoping for free, and one that arranges regions of its own gets it by
placing its placeholders the same way.

Every `<raise>` of the bare pair *anywhere* under a rail child - the
handler's own state, and any depth within it - is rewritten. That is
decision 8b's "anywhere inside that group's rail subtree", and it is what
makes a host block type on a rail scoped on exactly the same rule as
`core.on_event` (8d): the rewrite is a property of the position in the
emitted chart, not of which type emitted the raise.

## Why it is idempotent, and why nesting composes

A child's own pass has already run by the time its parent sees it, so an
inner group's rail raises arrive here already salted with the *inner*
group's id. Those names are not the bare pair, so this pass does not
touch them: an inner rail keeps its own salt and the outer salt is
applied only to what is still bare. The two halves - the transitions in
`Emit` and the raises here - therefore move together at every depth.

## What is not rewritten

A raise of the bare pair that is not inside a rail (decision 8c). It
reaches no rail today and none after, and leaving it alone is what keeps
the compiled bytes of a document with no interruptible group identical to
what they were before this pass existed - a document with no rail has no
rail child, so `scope/2` answers with the children it was handed.

# `scope`

```elixir
@spec scope(StatifierBlocks.Emission.t(), [
  {StatifierBlocks.Block.id(), StatifierBlocks.Emission.t()}
]) ::
  [{StatifierBlocks.Block.id(), StatifierBlocks.Emission.t()}]
```

Salts the interrupt raises inside `emission`'s rail children.

`emission` is one block's own emission, before its children are spliced
in, and `compiled_children` is the `{block id, emission}` list the
compiler already holds after emitting them. Returns `compiled_children`
with each rail child's subtree rewritten, and returns it unchanged when
`emission` carries no rail - which is every block in every document that
holds no interruptible group, so no such chart moves a byte.

---

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