The result of Enact.dry_run/3 — a distinct struct so callers are
structurally unable to confuse "validated" with "executed".
updates—Enact.updates/2output: the canonical, post- normalization "what will be persisted" map. What the user confirms is definitionally what executes. Patch previews carry only the provided keys; host apps render old → new diffs by comparing againstsubject.subject— the recordload_subject/2returned, ornil. Compareupdatesagainst it for old → new diffs. Not part of the digest.resolved— names of resolvers that succeeded. Never the loaded structs — those stay inctx.assignsand never reach the caller.digest— canonical hash binding the action, mode, locator params (params keys not in the input schema), and updates map. Pass it back asconfirm_digest:toEnact.run/3, which recomputes post-validation and returns:conflicton mismatch.
A preview is not a promise: no reservation semantics. The confirming
run/3 re-executes the full pipeline, and races surface as
:invalid/:conflict normally.
Summary
Functions
Canonically digests a pending change: "sha256:..." over the action
module, mode, locator params, and updates map together. Locator params
are params keys not in the input schema (fields/1) — typically the
URL-anchored subject id. Folding action, mode, and locators in makes
"the user confirmed this exact change to this record" total — a
digest minted for one action, mode, or subject never confirms another,
and input-less actions (whose updates are always %{}) don't collapse
across records.
Types
Functions
Canonically digests a pending change: "sha256:..." over the action
module, mode, locator params, and updates map together. Locator params
are params keys not in the input schema (fields/1) — typically the
URL-anchored subject id. Folding action, mode, and locators in makes
"the user confirmed this exact change to this record" total — a
digest minted for one action, mode, or subject never confirms another,
and input-less actions (whose updates are always %{}) don't collapse
across records.
The encoding is hand-rolled and injective — every node is type-tagged,
binaries and atom names are length-prefixed, and map entries are sorted
by encoded key at every depth. Elixir map ordering alone is not
sufficient (large maps enumerate in hash order), and the encoding avoids
term_to_binary, whose bytes are not guaranteed stable across OTP
releases — digests must survive the confirmation gap in a mixed-version
rolling deploy.