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

Which generated element came from which block (ADR-0004 decision 5).

The compile runs one way and nothing decompiles a chart back into
blocks. What has to survive the trip is *correspondence*, and this is it.

## Keyed two ways, because it answers two different questions

*Runtime highlighting* starts from a running session, whose active
configuration is a list of **state ids**
(`Statifier.active_leaf_states/1`). That is `by_state_id`, and
`StatifierBlocks.Compiler.StateId`'s totality is what makes it complete.

*Error routing* starts from an upstream finding, and upstream findings
carry no element reference at all: every diagnostic in statifier's
pipeline is `{reason, message, location}` where `location` is a
`%Statifier.Parser.Location{}`, and the offending ids ride *inside* the
closed `reason` tuple as data. So the second key is a **byte span over
the generated SCXML**, and mapping a finding is `owner_at/2` on
`location.start_offset`.

A span key is strictly more general than an id-based one. It routes
findings about transitions, which have no id of their own in SCXML, and
findings whose reason tuple carries no id at all - and because the
compiler records the span of every attribute *value* as well as every
element, it routes a bad expression to the config field the author typed
it into.

## The map is total over the emission

Every span the compiler emits has an owner; there is no unowned
scaffolding. Chart-level elements belonging to no particular block - the
`<scxml>` element itself - are attributed to the **root block**, which
ADR-0001 decision 1 guarantees exists. Totality is what makes decision
9's mapping a total function rather than one with an `:unmapped` arm
every consumer must handle and none can act on. An unmapped offset
inside the generated bytes is a compiler bug, and `owner_at/2` still
returns an error tuple for it rather than raising, because a caller may
hand it an offset from a *different* chart.

## Serialization

`to_json/1` writes the map under ADR-0001 decision 8's canonical rules,
so a host can store it beside the SCXML, diff it, and read it from
non-Elixir tooling. It is not part of the document and carries no
`schema_version` of its own; it is versioned by the compilation record's
compiler version.

# `owner`

```elixir
@type owner() :: %{
  block_id: StatifierBlocks.Block.id(),
  role: String.t() | nil,
  config_key: String.t() | nil
}
```

`role` is `nil` for the block's own state and a role name for one it
minted (ADR-0004 decision 3). `config_key` is set when the span was
emitted verbatim from that config field, which is what makes a finding
the author's rather than a bug in this package.

# `span`

```elixir
@type span() :: {start_offset :: non_neg_integer(), end_offset :: non_neg_integer()}
```

Byte offsets into the generated SCXML. Exclusive end, as upstream.

# `t`

```elixir
@type t() :: %StatifierBlocks.Provenance{
  by_state_id: %{optional(String.t()) =&gt; owner()},
  spans: [{span(), owner()}]
}
```

# `from_json`

```elixir
@spec from_json(binary()) :: {:ok, t()} | {:error, term()}
```

Reads back what `to_json/1` wrote.

# `owner`

```elixir
@spec owner(
  StatifierBlocks.Block.id(),
  keyword()
) :: owner()
```

Builds an owner. `opts` carries `:role` and `:config_key`, both `nil` by
default.

# `owner_at`

```elixir
@spec owner_at(t(), non_neg_integer()) ::
  {:ok, owner()} | {:error, {:unmapped_offset, non_neg_integer()}}
```

The owner of the innermost span containing `offset`.

Innermost is by width: spans nest, so the narrowest span containing an
offset is unique, and an attribute value's span is always strictly
inside its element's.

# `owner_of_state`

```elixir
@spec owner_of_state(t(), String.t()) :: {:ok, owner()} | :error
```

The owner of the generated state `state_id`, for mapping a running
session's `Statifier.active_leaf_states/1` back onto blocks.

# `owners_of_states`

```elixir
@spec owners_of_states(t(), [String.t()]) :: [owner()]
```

The blocks a running configuration is inside, in the order the
configuration was given, with any state this map does not know dropped.

The drop is deliberate rather than lenient: a state id this map has
never heard of is a state from a *different* chart, and a highlighting
caller wants the blocks it can highlight rather than an error over the
whole list.

# `to_json`

```elixir
@spec to_json(t()) :: binary()
```

Canonical JSON, ADR-0001 decision 8's rules. Deterministic.

---

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