Pure projection of a single captured row change into deterministic, JSON-friendly maps.
Authority
from_audit_change/2 is a pass-through of persisted audit_changes columns only.
It does not query the database, re-apply Threadline.Capture.RedactionPolicy, or invent
values that capture did not store. Low-information rows (masked columns, sparse
changed_from) are expected and honest.
Relationship to Threadline.Export: the :export_compat format mirrors the
base string-key map produced by export's internal change_map/1 ("op",
"data_after", "changed_fields", "changed_from", identifiers). Phase 31
omits nested "transaction" and "action" objects; full export rows may still
include those when preloaded. Use export for CSV/NDJSON documents; use this module
when you need field_changes or the same triple without join metadata.
schema_version
The primary wire map includes "schema_version" => 1. Future additive fields may
bump this integer; consumers should treat unknown keys as forward-compatible.
INSERT / UPDATE / DELETE matrix
| Op | data_after | changed_fields / changed_from | Default field_changes |
|---|---|---|---|
| INSERT | row snapshot | N/A for delta semantics | [] (row snapshot is authoritative via "data_after") |
| UPDATE | row after image | capture-driven delta | One entry per name in changed_fields (only), sorted by "name" |
| DELETE | nil | N/A | [] — no synthetic per-column removals without a stored pre-image |
before_values and per-field prior epistemics
"none"—changed_fromisnil: integrator did not store before-values. UPDATE field entries omit"before"/"prior"keys entirely (only"after")."sparse"—changed_fromis a map (including%{}): prior values may be incomplete. If a column inchanged_fieldshas no key inchanged_from, the field object includes"prior_state" => "omitted"instead of inventing a scalar. When the key exists,"before"reflects JSON truth (including JSONnull).
except_columns and masking
UPDATE projection iterates only changed_fields (nil-safe), not
Map.keys(data_after), so columns present in data_after but excluded from capture's
delta lists do not appear as false positives.
Values in data_after / changed_from pass through unchanged—including stable
placeholders such as :mask where capture stored them (same class of values as
export JSON).
Options
:format— set to:export_compatfor the export-aligned flat map (seefrom_audit_change/2@doc).:expand_insert_fields— defaultfalse; whentrueon INSERT, derives"kind" => "set"field rows from keys indata_afteronly (presentation-only; not per-field capture facts).
Summary
Functions
Builds a deterministic map for audit_change.
Functions
Builds a deterministic map for audit_change.
Primary format (default)
String keys throughout, including "schema_version", "before_values" ("none" or
"sparse"), "field_changes" (lexicographically sorted by "name"), and core row
identifiers compatible with integrator expectations ("op", "id", "transaction_id",
"table_schema", "table_name", "table_pk", "captured_at" as ISO-8601 UTC,
"data_after").
:export_compat
When opts contains format: :export_compat, returns a single flat string-key map
aligned with Threadline.Export change_map/1 base fields: "id",
"transaction_id", "table_schema", "table_name", "op", "captured_at",
"table_pk", "data_after", "changed_fields", "changed_from". IDs are coerced
with to_string/1; table_pk defaults to %{}, changed_fields to [],
changed_from to %{} when nil. Nested "transaction" and "action" are not
included in Phase 31 unless a later phase adds optional preload parameters.