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

The Chart stage: run the generated SCXML through statifier's own
pipeline and route every finding back to a block (ADR-0004 decisions 9
and 10).

This package ships **no** reachability analysis, no transition-target
check, no id-uniqueness check and no expression well-formedness check.
Statifier's validator already runs those against a conformance corpus
with a regression ratchet behind it (st-ADR-0006), and a second semantic
validator written against block documents would be a second
implementation of the same rules that must agree with the first - every
disagreement surfacing as a document the editor accepts and the engine
then rejects. So: generate the SCXML, run it through
`Statifier.compile/2`, and map the findings back through provenance.
Adding a chart-semantic check here is a change to ADR-0004 decision 9.

## How the routing works

Upstream findings carry no element reference. Every diagnostic in the
pipeline - `Statifier.Parser.ParseError`, `Statifier.Lowering.Error`,
`Statifier.Validator.Error`, `Statifier.Validator.Warning`,
`Statifier.Compiler.Error` - is `{reason, message, location}`, where
`location` is a `%Statifier.Parser.Location{}` over the source bytes.
So the route is `location.start_offset` through
`StatifierBlocks.Provenance.owner_at/2`, and decision 5's totality is
what makes it a total function rather than one with an `:unmapped` arm.

Upstream's `message` survives verbatim, and its `reason` tag becomes the
finding's `code`. Upstream's document-order sort survives as document
order over blocks, which `StatifierBlocks.Compiler` applies to every
stage's findings alike.

## The fault split (decision 9)

Two kinds of chart-stage finding, distinguished by exactly one thing -
whether the owning span carries a config key:

  * **Structural findings are bugs in this package or in a host's block
    type, never the author's doing.** `{:unresolved_target, id}`,
    `{:initial_not_descendant, id, parent}`, a malformed namespace: an
    author cannot express any of these, because the block vocabulary has
    no way to name them. Their owning span has `config_key: nil`, and
    `fault: :package` is the actionable part - "this cannot be fixed
    here" is the only honest message.
  * **Content findings are the author's, and they carry a config key.**
    An `:expression` config field is a predicator source string passed
    verbatim into a `cond`; if it does not parse, upstream returns
    `{:expression_compile_error, owner_ref, source, parse_error}` located
    at the attribute value - a span this package recorded with the config
    key the value came from. That is squarely the author's typo, and the
    config key is what lets an editor put the error on the field they
    typed into rather than on the block as a whole.

An upstream **error** of either kind fails the compile; a warning does
not, and rides on `StatifierBlocks.Compiled`.

## The sub-expression span (decision 9's last refinement)

A content finding routes to a field. Decision 9 goes one step further:
the predicator parse error carries a span *within the expression
string*, so an editor can underline the offending sub-expression inside
the field rather than the whole field. That span is composed here and
lands on the finding as `config_value_span`.

The composition is three steps, and the middle one is upstream's:

  1. Upstream reports the failure against the **attribute value's** span
     in the generated SCXML (`Statifier.Compiler.Error`'s `location`),
     and carries predicator's own `{line, column}` span over the
     expression string it compiled.
  2. `Statifier.Parser.Location.resolve_span/4` composes the two into an
     absolute span over the generated document, walking the raw bytes
     and the entity-expanded string in lockstep so a `&gt;` earlier in
     the expression cannot shift the answer.
  3. That absolute span is run **backwards** through the same
     correspondence the provenance map is built from: subtract the
     attribute value's own start, and unescape the prefix, which turns
     generated-document bytes back into bytes of the value the author
     typed.

Step 3's unescape is not decoration. A `cond` is emitted through
`StatifierBlocks.Compiler.Serializer`'s XML escaping, so the canonical
`amount > > 5000` reaches the document as `amount &gt; &gt; 5000`, where
the offending second `>` sits at byte 12 rather than at byte 9 - one
entity reference ahead of it is already enough to move it. The offsets
this field carries are into the author's value, so they are the ones an
editor can use without knowing that a serializer exists.

### When a finding gets one

All four have to hold, and the field is `nil` otherwise:

  * the finding's owning span carries a **config key** - the same test
    the fault split above turns on, and the reason a canonicalised
    attribute (`core.wait`'s `delay`, whose bytes the block type
    rebuilt rather than passed through) is never annotated and so never
    offered an in-value offset;
  * the diagnostic is an `:expression_compile_error`, the only upstream
    reason that carries an expression string at all;
  * predicator supplied a **span** for the failure (`ParseError`'s
    `:span` is optional, and `nil` on an error built through `new/3`);
  * the resolved span lies inside the attribute value's own span - the
    guard that keeps a degraded resolution honest rather than turning it
    into an offset into somebody else's bytes.

`resolve_span/4` degrades rather than raising: an expression whose
expanded text does not describe the raw slice resolves to the value's
whole span, which arrives here as "underline the entire field" - the
same answer a consumer would have reached with no span at all, which is
why it is passed through rather than discarded.

### Why "carries a config key" is enough

The first bullet reads "carries a config key", where decision 9's
annotation rule means "was written verbatim from that config value".
Those coincide because the annotation is only ever left on a verbatim
value: a block type that *composes* an attribute leaves it
unannotated, so its bytes have no config key and a finding inside them
is the package's. `core.subchart` was the one exception - it annotated
a `cond` it builds from an outcome name with the config key
`outcomes` - and that annotation was dropped rather than this
criterion widened.

# `validate`

```elixir
@spec validate(binary(), StatifierBlocks.Provenance.t(), StatifierBlocks.Document.t()) ::
  {:ok, [StatifierBlocks.Compiler.Finding.t()]}
  | {:error, [StatifierBlocks.Compiler.Finding.t()]}
```

Compiles `scxml` upstream and maps what comes back.

`{:ok, warnings}` when the chart is valid - upstream's warnings
(st-ADR-0033 made `Machine.warnings/1` their only surfacing seam),
mapped the same way errors are. `{:error, findings}` otherwise.

---

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