# `StatifierUI.Trace.Diagnostic`
[🔗](https://github.com/riddler/statifier-ui/blob/v0.11.0/lib/statifier_ui/trace/diagnostic.ex#L1)

The wire `error` object: what `docs/wire-format.md` documents as an event
object's `error` key.

It has two arms, discriminated on the wire by `"class"` (ADR-0014).
`object/4` renders an expression failure (`class: "expression"`) and is
the bulk of this module - spans, anchors, and the `location_kind`
discipline below are all its. `reason_object/4` renders a non-value
reason term (`class: "reason"`), peeling `{:nested_content, _, _}` first
so a wrapped expression failure comes back through `object/4` with its
span intact rather than as an opaque string.

This is the **only** caller of `Statifier.Parser.Location.resolve_span/4`
in this repo (ADR-0002 - never reimplement the engine's span composition
locally). Every span this module emits, and every location it composes
from one, follows that helper's convention: both the predicator span's
end and the returned location's end are **exclusive**.

`resolve_span/4` degrades rather than raising - a position past the end
of the value clamps to the value location's end, and a raw/expanded
desync returns the value location whole - so this module never rescues
it. Per project convention (CLAUDE.md, "errors are values / never
rescue-to-default at a leaf"), a call that could fail is not the shape
the failure takes here: the helper's own degraded return already is the
fallback.

A `nil` value location or a `nil` span means there is nothing to resolve.
`anchor/3` never calls `resolve_span/4` in either case - it returns
`{:node, _}` instead, so `object/4` falls back to the owning node's own
span. That branch is this module deciding *not to call* the helper, which
is a different thing from catching what it raises.

## `location_kind` reports what the producer did, not what the helper decided

`"resolved"` means `resolve_span/4` was called with a value location and
a span. `"node"` means it was not (no span, or no value location) and the
owning node's own span was emitted instead. This module never inspects
the *result* to guess which happened - `resolve_span/4`'s own degraded
return (falling back to the value location whole) is indistinguishable
from a genuine resolution by looking at the location alone, and modeling
that distinction here would be exactly the ADR-0002 failure this module
exists to prevent. So `"resolved"` may still span the whole attribute
value when the helper degraded internally.

# `anchor`

```elixir
@spec anchor(
  Statifier.Event.Cause.origin() | nil,
  Statifier.Machine.t(),
  Statifier.Evaluator.Error.t() | nil
) ::
  {:value, Statifier.Parser.Location.t()}
  | {:node, Statifier.Parser.Location.t()}
  | :none
```

Where to anchor `error`'s span for `origin`: the value location of the
failing expression (`{:value, _}`, resolved against `error.span` by the
caller), the owning node's own span with nothing further to resolve
(`{:node, _}`), or nothing at all (`:none`).

Dispatches on `origin` and, for content, on the content struct itself -
never on a generic `Map.get(node, :location) || Map.get(node, :node_location)`
reflection, the same posture `StatifierUI.Trace.Manifest`'s own
content-location dispatch takes and for the same reason: that fallback
is silently wrong for
`Statifier.Machine.Content.Assign`, whose `:location` is a path-expression
*string*, not a span.

A candidate value location is only ever returned as `{:value, _}` when
`error.span` is non-nil - a `nil` span means `resolve_span/4` would not be
called, so the caller must not be handed a location that invites it.

`error` of `nil` is ADR-0014's `class: "reason"` arm: there is no
expression and no span, so every origin that names a node at all returns
`{:node, _}` and nothing ever returns `{:value, _}`.

# `object`

```elixir
@spec object(
  Statifier.Evaluator.Error.t(),
  Statifier.Event.Cause.origin() | nil,
  Statifier.Machine.t() | nil,
  String.t() | nil
) :: map()
```

The wire `error` object for `error`, raised with cause `origin`.

`"class"` is always `"expression"` here - ADR-0014's discriminator,
explicit on both arms rather than absent-means-expression, so a consumer
never infers the class from a key's absence. `"kind"` and `"expression"`
are always present. `"span"` is present only
when `error.span` is non-nil. `"location"`/`"location_kind"` are present
only when both `machine` and `source` are supplied *and* `anchor/3` finds
a location to anchor on - `origin` of `nil`, `machine`/`source` of `nil`,
or an origin `anchor/3` cannot resolve at all all omit both keys, key
absence being the ADR-0005 discipline for "nothing here" rather than a
null or a sentinel.

# `reason_object`

```elixir
@spec reason_object(
  term(),
  Statifier.Event.Cause.origin() | nil,
  Statifier.Machine.t() | nil,
  String.t() | nil
) :: map()
```

The wire `error` object for a **non-value reason** term - ADR-0014's
`class: "reason"` arm, and the terms it peels on the way there.

`term` is the unconstrained `reason` `Statifier.Interpreter.Content`
raises an execution error with, which reaches an `error.execution` or
`error.communication` event's `data` as a tagged tuple, a bare atom, or
anything else at all. Three things happen to it here, in this order:

1. **`{:nested_content, c_index, inner}` is peeled**, repeatedly,
   collecting each `c_index` in order into `"content_path"`. The wrapper
   is what an `<if>` partition or a `<foreach>` body puts around a failure
   raised inside it, and treating it as opaque would leave every nested
   expression failure rendering as a string - ADR-0014 decision 4.
2. **An innermost `%Statifier.Evaluator.Error{}` routes to `object/4`**,
   so a wrapped expression failure keeps its span and its location and
   renders as `class: "expression"`, with `"content_path"` beside it.
3. **Anything else renders as `class: "reason"`**: `"kind"` derived from
   the term's shape by `reason_kind/1`, and `"reason"` the whole term's
   `inspect/1`.

`"reason"` is documented on the wire as human-readable text rather than
structured data to branch on, the same wording `session.terminated`'s
`reason` carries. `"kind"` is the branchable half.

`"location"`/`"location_kind"` are present only when the producer could
anchor, and on this arm the anchor is always the owning node's own span
(`location_kind: "node"`): there is no expression span to resolve
against, so `anchor/3` is called with no error at all.

---

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