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

Turns a compiled `%Statifier.Machine{}` plus caller-supplied context into
the `session.start` definition message (`docs/wire-format.md`) - the
message that makes every later index (state `index`, `t_index`,
`c_index`, `d_index`) resolvable to a source location without a compiler
on the reading end.
Pure, no process.

The Machine does not retain the SCXML source text and has no
`id -> index` reverse map beyond the partial `id_to_index`
(`deps/statifier/lib/statifier/machine.ex:29`), so the state table is built
by walking `machine.states` (a tuple in document order) rather than by any
lookup, and `source` is accepted from the caller rather than derived
(decision 7 of the plan).

## The `Content.Script`/`Content.Assign` location gotcha

Every executable-content node kind carries a `:location` field holding its
own `%Statifier.Parser.Location{}` span, with two exceptions this module
handles explicitly rather than by a generic fallback:

- `Statifier.Machine.Content.Script` has no `:location` field at all - its
  span lives on `:node_location` instead
  (`deps/statifier/lib/statifier/machine/content/script.ex:48`).
- `Statifier.Machine.Content.Assign` has *both* fields, but its `:location`
  is a `String.t()` - the raw, uncompiled `location` attribute (a path
  expression such as `"foo.bar"`), not a location span. Its span is on
  `:node_location`
  (`deps/statifier/lib/statifier/machine/content/assign.ex:42-46`).

A naive `Map.get(node, :location) || Map.get(node, :node_location)`
handles `Script` correctly (its `:location` key does not exist, so
`Map.get/2` returns `nil` and the fallback wins) but silently picks the
wrong value for `Assign` - its `:location` is a truthy string, so the `||`
never reaches `:node_location`, and a string ends up where a location
object belongs. `content_location/1` below dispatches on the struct
itself instead, naming `Assign` and `Script` as the two kinds whose span
is `:node_location` and treating every other kind's `:location` as the
span it already is.

## `attribute_locations`: key presence is the contract

A state row and a transition row each carry an `attribute_locations`
object alongside the element-level `location`, built from the Machine
field of the same name (`Statifier.Machine.State`,
`Statifier.Machine.Transition`). It maps an attribute name to that
attribute's own value span, and its value is not the interesting part:
**an entry exists only for an attribute the author actually wrote**, so
`Map.has_key?(row["attribute_locations"], "type")` is the "was `type`
written" question that `type`'s own value cannot answer once lowering has
applied the `:external` default. The map is carried verbatim from the
Document node through the Machine, so that contract survives the whole
trip to the wire.

The object is always present and is `%{}` for an element that wrote no
attributes at all, for the synthesized initial transition no author wrote,
and for a Machine compiled by an engine old enough not to populate the
field. Those three are indistinguishable on the wire and deliberately so:
a consumer that finds no entry for the attribute it wants falls back to
the row's element-level `location`, which is exactly the granularity the
format offered before this field existed.

`cond_location` stays where it is. It is not folded into this object
because it carries a fallback the raw map does not - the transition's own
`location` when `cond` was written without a recorded span - so the two
answer different questions. Prefer `attribute_locations["cond"]` for new
work; read `cond_location` when the fallback is what is wanted.

# `opts`

```elixir
@type opts() :: [
  source: String.t(),
  fixtures: map(),
  parent_session: String.t(),
  invokeid: String.t()
]
```

Caller-supplied context `build/3` cannot derive from the Machine alone.

# `build`

```elixir
@spec build(Statifier.Machine.t(), session :: String.t(), opts()) ::
  {:ok, StatifierUI.Trace.Message.t()} | {:error, term()}
```

Builds the `session.start` message for `machine`, stamped `session:
session` and `seq: 0`.

Returns `{:error, {:invalid_fixtures, opts[:fixtures]}}` when `:fixtures`
is supplied and is not a map, and `{:error, {:invalid_source,
opts[:source]}}` when `:source` is supplied and is not a binary. Nothing
else can fail: every other field comes from a compiled `Machine`, which is
well-formed by construction.

# `version`

```elixir
@spec version() :: pos_integer()
```

The `session.start` schema version this module produces - `1` (ADR-0005's initial value).

---

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