Folds a [StatifierUI.Trace.Message.t()] (in any order) into a readable
log keyed by (macrostep, round), per the sui-t36.5 plan.
build/1 is a pure regrouping, not an interpretation: it decides where
each message goes, never what it means. Ordering comes entirely from the
stamps the producer put on each message, never from arrival order or
seq alone - docs/wire-format.md:91-94 names (macrostep, round) as
the format's own timeline key. Feeding build/1 the same messages in a
different order returns an identical struct.
Bucketing, not sorting
Buckets are ordered by (macrostep, round), and only within a bucket
do messages get a further order, by {microstep, seq}. This is two
levels, not one flat {macrostep, round, microstep, seq} sort key: a
round is a bucket a message either belongs to or does not, so a message
stamped at a lower microstep than an earlier round's messages still
lands in its own round's bucket rather than sorting ahead of them. seq
is itself a producer stamp (docs/wire-format.md:84-89), so using it as
the final tiebreak is still ordering from the stamps.
Routing effect.* messages
The routing rule is "does this message carry a round", never a check
against the "effect." prefix. When this module was written only
trace.* messages and effect.budget_exhausted carried round, so the
other effect.* types landed in their macrostep's effects; sui-67d
then propagated round onto every effect.* type, and - exactly as
planned - that was a pure data change needing no change here: an
effect.* message carrying a round is routed to its round bucket, and
effects still catches any message without one (an older recorded
stream, for example - the must-ignore rule cuts both ways).
Errors
docs/wire-format.md:96-102 forbids merging two sessions' stamps onto
one timeline (statifier ADR-0050 lets one mailbox carry an invoke tree of
related sessions), so build/1 refuses a message list naming more than
one session id rather than corrupting the log.
Summary
Functions
Folds messages into an t().
Types
@type t() :: %StatifierUI.EventLog{ macrosteps: [StatifierUI.EventLog.Macrostep.t()], session: String.t() | nil, session_messages: [StatifierUI.Trace.Message.t()], truncated?: boolean() }
Functions
@spec build([StatifierUI.Trace.Message.t()]) :: {:ok, t()} | {:error, {:mixed_sessions, [String.t()]}}
Folds messages into an t().
Returns {:error, {:mixed_sessions, sorted_ids}} when messages names
more than one distinct session. An empty list returns an empty, default
log with session: nil.