Pure, deterministic structural diff of two excessibility.digest/v1 maps.
Given a base and head digest (as produced by Excessibility.Digest,
whether reloaded via Jason.decode!(..., keys: :atoms), decoded with string
keys, or a raw Elixir map), diff/2 returns a stable, sorted structural
delta. It emits no verdict, no severity beyond advisory, and no merge
decision — only the observed structural differences plus measurement-scope
notes.
The measurement-scope guard
A signal delta is asserted only when both digests measured that signal:
- Queries/plans are diffed only when both
capture.ecto_configuredare true. Otherwise the queries/plans lists stay empty and acoverage.notesentry explains that one side did not measure queries. - Plans are diffed only when both
capture.plan_capturemodes match. A difference is scope-noted, never reported as a plan change. - The plan-variant set per SQL fingerprint is bounded by the digest, so a side
can drop structures it never emitted. When either side omitted variants the
incompleteness is made explicit — a forced
plansentry carrying per-sidebase_variants_omitted/head_variants_omittedcounts, plus acoverage.notesentry — so a bounded comparison never renders "no change" for a structure it could not see (issue #183). - Assign deltas are diffed only when the
assign_sizesenricher ran on both sides; other enricher differences are scope-noted. - A
schemamismatch adds a prominent note.
This keeps compare honest: a change in what was measured can never masquerade as a real regression.
Key tolerance
Every field read goes through get/3, which tries the atom key then the
string key, so atom-keyed and string-keyed digests behave identically.
Determinism
All output lists are sorted by a stable key (fingerprint,
{view, callback}, or name) and all set math is order-independent, so the
diff of a given pair is byte-for-byte reproducible regardless of input event
order.
Summary
Functions
Diff two decoded digest maps. Returns the structural delta described in the module docs. Never raises for well-formed digest maps; missing fields fall back to empty defaults.