Versioned, privacy-aware explanation record for one runtime decision.
Input and reply content are absent by default. Applications must opt in to content recording through their journal configuration.
Summary
Functions
Builds a journal record and derives a stable record identifier from its turn, phase, sequence, and agent.
Restores a persisted schema-v1 record while tolerating unknown additive fields. Legacy maps without a schema version are migrated to version 1.
Converts a record into a JSON-safe, string-keyed map for persistence.
Types
@type t() :: %Spectre.Journal.Record{ agent: module() | nil, agent_version: term(), conversation_id: term(), decision: map(), duration_native: integer() | nil, effect: map() | nil, evidence: [map()], id: String.t(), input: map() | nil, metadata: map(), occurred_at: DateTime.t(), phase: atom(), policy: map() | nil, reason: map(), reply: String.t() | nil, schema_version: pos_integer(), sequence: non_neg_integer(), state_revision: non_neg_integer() | nil, trace_id: term(), transition: map() | nil, turn_id: term() }
Functions
Builds a journal record and derives a stable record identifier from its turn, phase, sequence, and agent.
Restores a persisted schema-v1 record while tolerating unknown additive fields. Legacy maps without a schema version are migrated to version 1.
Converts a record into a JSON-safe, string-keyed map for persistence.
Record fields carry arbitrary runtime terms — atoms, tuples, structs,
DateTimes — that JSON columns cannot store. This conversion is lossy but
total: atoms become strings, tuples become lists, structs lose their module,
calendar types render ISO-8601, and anything else falls back to inspect/2.
Journal store adapters can persist the result directly:
def append(record, _opts) do
record |> Record.to_json_map() |> MyRepo.insert_journal_row()
end