The :telemetry surface for this package's storage-phase seams
(ADR-0009) - the single definition site for every
[:statifier_persistence, ...] event name, and the one module in lib/
that calls :telemetry.execute/3.
docs/telemetry.md is the full contract: what each event answers, what
it deliberately leaves to statifier-ex and to opentelemetry_ecto, and
what opentelemetry_statifier does with it. This moduledoc is the
reference table; that note is the reasoning.
events/0 returns every name below, built from the same literal-atom
lists the emitters use, so the bridge can attach one handler per event
name without hand-copying the list (ADR-0009 decision 8,
ots-ADR-0003).
This module owns family two only. The interpreter's own family -
[:statifier, :session, ...] with driver: :persistence - is emitted
by calling Statifier.Telemetry directly from the stepper seam and is
deliberately not wrapped here: a wrapper would be the second
implementation st-ADR-0067 decision 2 exists to prevent.
Structural rules (ADR-0009 decisions 3, 5, 8, 9)
- The prefix is
[:statifier_persistence, ...], fixed and not configurable. The bridge must name the events at compile time, and a per-host prefix would make its attach list depend on host configuration it cannot see. - Measurements are numbers; metadata is everything else, integer
indexes included -
child_indexis metadata, because an opaque index has no numeric meaning to average. - The step seam is the one span. This package owns an interval
nobody else measures - lock, load, decode, identity-check, advance,
execute effects, persist - and the upstream macrostep span nests
inside it. It opens with
:startand closes with exactly one of:stopor:exception.span_refis a freshmake_ref/0per span, carried on both halves, and is the only pairing key (st-ADR-0040decision 2). Everything else is a single point-in-time event. execution_idis the identity key, neverscope;session_idrides only where a position has already been decoded and is explicitlynilotherwise.- Emission is unconditional. There is no config knob and no sampling
knob:
:telemetry.execute/3on an event with no handlers is a lookup and a return. - Amendment discipline. Adding a measurement or a metadata key to an
existing event is an amendment and is fine; renaming or removing one,
renaming an event, or changing the
:persistencedriver atom is breaking and needs a new ADR.
The step seam
Brackets one serialized drive - create/4, step/5, fail/4 or
cancel/3 inside StatifierPersistence.Executions's own serialized/5.
Emitted on the calling process. The
[:statifier, :session, :macrostep, ...] span opens and closes inside
it.
| Event | Measurements | Metadata |
|---|---|---|
[:statifier_persistence, :execution, :step, :start] | system_time, monotonic_time | execution_id, entry, span_ref |
[:statifier_persistence, :execution, :step, :stop] | duration, monotonic_time | execution_id, session_id, content_hash, entry, outcome, status, reason, span_ref, invoke_id, child_count |
[:statifier_persistence, :execution, :step, :exception] | duration, monotonic_time | execution_id, entry, span_ref, kind, reason, stacktrace |
[:statifier_persistence, :execution, :lock] | duration, system_time | execution_id, strategy, outcome, reason |
entry is which public door was used: :create, :step,
:done_invocation, :failed_invocation, :answer_parent, :fail,
:cancel. outcome on the stop is :ok, :discarded or :error.
[:statifier_persistence, :execution, :lock]'s duration is the wait for
the per-execution exclusion, not the held time, and its outcome is
:acquired or :unavailable. It is also emitted by
StatifierPersistence.Executions.unpark/3, which takes the same exclusion
and opens no step span.
invoke_id and child_count on the stop are nil on every ordinary
drive and set on the entry: :answer_parent step a child takes on its
parent's behalf, so the step span carrying a fan-out's whole assembled
answer is recognisable as that one (the ADR-0009 sp-8wv amendment).
child_count is nil for a single-child subchart.
:exception closes the span in place of :stop when anything inside
the drive raises, throws or exits - a host executor, an event builder,
an adapter or the serialization strategy - and the raise then reaches
the caller unchanged, with its original stacktrace. Its keys are the
ones :telemetry.span/3 puts on its own :exception event: the start
half's metadata plus kind, reason and stacktrace. The raised term
and its stacktrace can carry any value the failing code held - an event
builder's is the decoded machine state, datamodel included - so both are
narrowed before the event is emitted. reason is the exception's module
for an :error (a raw Erlang error is normalized first, so a failed match
reports MatchError), and for a :throw or :exit the thrown or exit
atom, or :redacted for any other term. stacktrace keeps each frame's
module and function, replaces an argument list by its arity, and keeps
only :file and :line of the location. The caller's re-raise keeps
the original reason and stacktrace.
The storage seam
| Event | Measurements | Metadata |
|---|---|---|
[:statifier_persistence, :adapter, :call] | duration, system_time | adapter, callback, outcome, reason, execution_id, session_id, content_hash |
[:statifier_persistence, :identity, :refused] | system_time | execution_id, session_id, stage, reason, stored_content_hash, supplied_content_hash |
callback is the StatifierPersistence.Storage.Adapter callback name, a
closed vocabulary fixed by the behaviour. stage on a refusal is
:position, :execution or :chart, and reason is :identity_mismatch or
:unidentified_chart; only the two content hashes travel, never the
Statifier.Machine.Identity structs the error term carries.
The execution lifecycle seam
| Event | Measurements | Metadata |
|---|---|---|
[:statifier_persistence, :execution, :created] | system_time | execution_id, session_id, content_hash, child?, metadata? |
[:statifier_persistence, :execution, :terminated] | system_time | execution_id, session_id, content_hash, status, driven_by, reason |
[:statifier_persistence, :execution, :discarded] | system_time | execution_id, entry, reason, repaired? |
[:statifier_persistence, :execution, :migrated] | system_time | execution_id, from_content_hash, to_content_hash, dropped |
[:statifier_persistence, :execution, :unparked] | system_time | execution_id, content_hash |
[:statifier_persistence, :effect, :failed] | system_time | execution_id, session_id, content_hash, kind, executor, reason, reentered? |
[:statifier_persistence, :drive, :turns_exhausted] | system_time, turns | execution_id, entry |
driven_by on :terminated is :chart or :host - fail/4 and
cancel/3 are the :host ones, and upstream emits nothing at all for
them. :discarded's reason is the closed vocabulary :terminal_execution,
:builder_declined, :position_terminal, and only the third sets
repaired?: true.
:migrated fires once per successful
StatifierPersistence.Executions.migrate/4, after its serialization
section returns (ADR-0013 decision 5). It carries both content hashes
under their own names, because a migration has two charts in hand, and
dropped is the list of dropped state ids that were in the execution's
configuration. A refused or parked migration emits nothing.
:unparked fires once per StatifierPersistence.Executions.unpark/3
that writes a :needs_migration execution back to :active, after its
serialization section returns. content_hash is the chart the execution
was parked on and goes on under. An unpark that writes nothing - of an
:active execution, of a terminal one, or one refused - emits no
:unparked.
The durable-subchart seam (ADR-0008)
| Event | Measurements | Metadata |
|---|---|---|
[:statifier_persistence, :child, :started] | system_time | parent_execution_id, child_execution_id, invoke_id, child_index, content_hash, session_id |
[:statifier_persistence, :child, :refused] | system_time | parent_execution_id, invoke_id, reason |
[:statifier_persistence, :child, :recorded] | system_time | parent_execution_id, child_execution_id, invoke_id, child_index, outcome |
[:statifier_persistence, :child, :answered] | system_time | child_execution_id, parent_execution_id, invoke_id, outcome, child_count, failed_count, delivery |
[:statifier_persistence, :child, :settled] | system_time, child_count, completed, failed, cancelled, unstarted | parent_execution_id, invoke_id, policy, decision |
[:statifier_persistence, :child, :cascade_cancelled] | system_time, count, retained | parent_execution_id, invoke_id |
content_hash on :started is the child's pinned hash (ADR-0008
decision 2). count on :cascade_cancelled is how many executions the sweep
actually cancelled and retained is how many it found already terminal
and left alone; both are legitimately 0.
:recorded fires once per fan-out child answer written under the
parent's exclusion, and :settled once per settlement decision -
decision is :answer or :not_yet - both from the settlement section
(the ADR-0009 sp-8wv amendment). :answered's outcome is the
invocation's aggregate for a fan-out, :failed when any index failed,
even though the parent's door is always done_invocation/5; and
child_count and failed_count are nil on the single-child path,
which is not an invocation with a width.
:answered's delivery is what the parent's door answered, a closed
vocabulary: :delivered, :discarded (the parent had already left the
invocation), :needs_migration (the parent is parked and refused the
answer whole, ADR-0014 decision 2) or :error (any other error). The
automatic answer returns the child's own result whatever the door
answered, so :needs_migration here is how a host learns that an answer
it must deliver again was refused.
Cardinality and disclosure
Every metadata key is bounded by the chart or by a closed vocabulary
except execution_id (and parent_execution_id / child_execution_id), which is
host-supplied and is a correlation id for a span or a log line, never
a metric dimension, and reason, which carries an arbitrary executor
or adapter term on some events and must be narrowed before it becomes a
dimension.
Nothing host-opaque and nothing from the datamodel is ever on an
event (ADR-0009 decision 7): not the chart_blob, the
position_blob, the identity_blob, the ADR-0006 metadata map, the
datamodel, an invoke's params, or a :done effect's donedata.
metadata? on [:statifier_persistence, :execution, :created] is a boolean -
whether a non-empty host map was supplied - and that is the whole of
what this contract says about it.
Summary
Types
What a parent's door answered to a child's answer - the delivery
metadata of [:statifier_persistence, :child, :answered].
Any event name this module emits.
Functions
Emits [:statifier_persistence, :adapter, :call], duration in
:native units around one storage-adapter callback.
Emits [:statifier_persistence, :child, :answered].
Emits [:statifier_persistence, :child, :cascade_cancelled] once per
public StatifierPersistence.Executions.cascade_cancel/3 call, after the
whole sweep - never once per node of the walk.
Emits [:statifier_persistence, :child, :recorded] - one fan-out
child's own answer, persisted on its own execution record inside the parent's
settlement exclusion.
Emits [:statifier_persistence, :child, :refused].
Emits [:statifier_persistence, :child, :settled] - one settlement
decision over a whole invocation, :answer or :not_yet.
Emits [:statifier_persistence, :child, :started].
Emits [:statifier_persistence, :drive, :turns_exhausted] - the drive
loop's own refusal, reported as a point-in-time verdict rather than a
span (ADR-0009 decision 5).
Emits [:statifier_persistence, :effect, :failed] - the executor seam's
verdict on one effect it accepted and could not perform.
Every [:statifier_persistence, ...] event name this package emits, in
the order docs/telemetry.md tables them.
Emits [:statifier_persistence, :execution, :created].
Emits [:statifier_persistence, :execution, :discarded].
Emits [:statifier_persistence, :execution, :lock]. duration is the wait
for the per-execution exclusion in :native units, never the held time.
Emits [:statifier_persistence, :execution, :migrated]: one execution
re-pinned from from_content_hash to to_content_hash by
StatifierPersistence.Executions.migrate/4 (ADR-0013 decision 5), or
one node of a tree re-pinned by
StatifierPersistence.Executions.migrate_tree/4 (ADR-0015 decision 5).
dropped lists the dropped state ids that were in its configuration.
Emits [:statifier_persistence, :execution, :step, :exception], the
step span's close when the drive raised, threw or exited, duration in
:native units measured from execution_step_start/3's reading.
Emits [:statifier_persistence, :execution, :step, :start] and returns the
System.monotonic_time/0 reading execution_step_stop/2 measures duration
against.
Emits [:statifier_persistence, :execution, :step, :stop], duration in
:native units measured from execution_step_start/3's reading.
Emits [:statifier_persistence, :execution, :terminated]. driven_by is
:chart for a :done/:budget_exhausted termination and :host for
StatifierPersistence.Executions.fail/4 and cancel/3, which no interpreter
runs on and which upstream therefore never reports.
Emits [:statifier_persistence, :execution, :unparked]: one
:needs_migration execution written back to :active by
StatifierPersistence.Executions.unpark/3 (ADR-0014's telemetry
amendment). content_hash is the chart it was parked on and goes on
under.
Emits [:statifier_persistence, :identity, :refused] - the deploy-drift
alarm.
Types
@type delivery() :: :delivered | :discarded | :needs_migration | :error
What a parent's door answered to a child's answer - the delivery
metadata of [:statifier_persistence, :child, :answered].
@type event_name() :: [atom(), ...]
Any event name this module emits.
@type fields() :: keyword()
A field list for one event: every key the contract names for it, in any
order. A key the caller omits is emitted as nil rather than dropped,
so a handler never has to Map.get/3 its way around a shape that
varies.
Functions
Emits [:statifier_persistence, :adapter, :call], duration in
:native units around one storage-adapter callback.
@spec child_answered(fields :: fields()) :: :ok
Emits [:statifier_persistence, :child, :answered].
child_count and failed_count are the invocation's, and are nil for
a single-child subchart, which has no invocation to aggregate. delivery
is a delivery/0: what the parent's door answered.
@spec child_cascade_cancelled( count :: non_neg_integer(), retained :: non_neg_integer(), fields :: fields() ) :: :ok
Emits [:statifier_persistence, :child, :cascade_cancelled] once per
public StatifierPersistence.Executions.cascade_cancel/3 call, after the
whole sweep - never once per node of the walk.
@spec child_recorded(fields :: fields()) :: :ok
Emits [:statifier_persistence, :child, :recorded] - one fan-out
child's own answer, persisted on its own execution record inside the parent's
settlement exclusion.
Every index but the last records an answer that never reaches the parent's door, so this is the only surface those answers appear on at all.
@spec child_refused(fields :: fields()) :: :ok
Emits [:statifier_persistence, :child, :refused].
@spec child_settled( counts :: %{required(atom()) => non_neg_integer()}, fields :: fields() ) :: :ok
Emits [:statifier_persistence, :child, :settled] - one settlement
decision over a whole invocation, :answer or :not_yet.
counts is the measurement map: child_count and the four tallies over
the invocation's indexes. They partition child_count only once every
index has an execution of its own, which is what makes unstarted worth
reading - it tells a fan-out still starting from one that is stuck.
@spec child_started(fields :: fields()) :: :ok
Emits [:statifier_persistence, :child, :started].
@spec drive_turns_exhausted(turns :: non_neg_integer(), fields :: fields()) :: :ok
Emits [:statifier_persistence, :drive, :turns_exhausted] - the drive
loop's own refusal, reported as a point-in-time verdict rather than a
span (ADR-0009 decision 5).
@spec effect_failed(fields :: fields()) :: :ok
Emits [:statifier_persistence, :effect, :failed] - the executor seam's
verdict on one effect it accepted and could not perform.
Nothing wraps a successful executor call: the host's work is the host's to instrument, and the step span already bounds it.
@spec events() :: [event_name(), ...]
Every [:statifier_persistence, ...] event name this package emits, in
the order docs/telemetry.md tables them.
This is what a bridge attaches to: ots-ADR-0003 attaches one handler
per event name under its own handler id, and it cannot do that for a
list it has to hand-copy.
@spec execution_created(fields :: fields()) :: :ok
Emits [:statifier_persistence, :execution, :created].
@spec execution_discarded(fields :: fields()) :: :ok
Emits [:statifier_persistence, :execution, :discarded].
Emits [:statifier_persistence, :execution, :lock]. duration is the wait
for the per-execution exclusion in :native units, never the held time.
@spec execution_migrated(fields :: fields()) :: :ok
Emits [:statifier_persistence, :execution, :migrated]: one execution
re-pinned from from_content_hash to to_content_hash by
StatifierPersistence.Executions.migrate/4 (ADR-0013 decision 5), or
one node of a tree re-pinned by
StatifierPersistence.Executions.migrate_tree/4 (ADR-0015 decision 5).
dropped lists the dropped state ids that were in its configuration.
Emits [:statifier_persistence, :execution, :step, :exception], the
step span's close when the drive raised, threw or exited, duration in
:native units measured from execution_step_start/3's reading.
The caller re-raises afterwards; this function only reports. The keys
follow :telemetry.span/3's own :exception event; the values of
reason and stacktrace are narrowed so no raised value, call argument
or location detail travels on the event (see the step seam section
above).
@spec execution_step_start( execution_id :: term(), entry :: atom(), span_ref :: reference() ) :: integer()
Emits [:statifier_persistence, :execution, :step, :start] and returns the
System.monotonic_time/0 reading execution_step_stop/2 measures duration
against.
Returning the reading rather than taking one is deliberate: it is the
same reading the monotonic_time measurement carries, so the span's
duration and the two halves' monotonic_time values cannot drift
apart.
Emits [:statifier_persistence, :execution, :step, :stop], duration in
:native units measured from execution_step_start/3's reading.
@spec execution_terminated(fields :: fields()) :: :ok
Emits [:statifier_persistence, :execution, :terminated]. driven_by is
:chart for a :done/:budget_exhausted termination and :host for
StatifierPersistence.Executions.fail/4 and cancel/3, which no interpreter
runs on and which upstream therefore never reports.
@spec execution_unparked(fields :: fields()) :: :ok
Emits [:statifier_persistence, :execution, :unparked]: one
:needs_migration execution written back to :active by
StatifierPersistence.Executions.unpark/3 (ADR-0014's telemetry
amendment). content_hash is the chart it was parked on and goes on
under.
@spec identity_refused(fields :: fields()) :: :ok
Emits [:statifier_persistence, :identity, :refused] - the deploy-drift
alarm.
Only the two content hashes ever travel, never the
Statifier.Machine.Identity structs an {:identity_mismatch, _, _}
term carries (ADR-0009 decision 7).