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.
Summary
Functions
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).
The wire error object for error, raised with cause origin.
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.
Functions
@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, _}.
@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.
@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:
{:nested_content, c_index, inner}is peeled, repeatedly, collecting eachc_indexin 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.- An innermost
%Statifier.Evaluator.Error{}routes toobject/4, so a wrapped expression failure keeps its span and its location and renders asclass: "expression", with"content_path"beside it. - Anything else renders as
class: "reason":"kind"derived from the term's shape byreason_kind/1, and"reason"the whole term'sinspect/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.