# `Enact.Preview`
[🔗](https://github.com/svycal/enact/blob/v0.1.0/lib/enact/preview.ex#L1)

The result of `Enact.dry_run/3` — a distinct struct so callers are
structurally unable to confuse "validated" with "executed".

  * `updates` — `Enact.updates/2` output: 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 against
    `subject`.
  * `subject` — the record `load_subject/2` returned, or `nil`. Compare
    `updates` against it for old → new diffs. Not part of the digest.
  * `resolved` — names of resolvers that succeeded. Never the loaded
    structs — those stay in `ctx.assigns` and 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 as `confirm_digest:` to `Enact.run/3`, which recomputes
    post-validation and returns `:conflict` on 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.

# `t`

```elixir
@type t() :: %Enact.Preview{
  action: module(),
  digest: String.t(),
  mode: :create | :patch,
  resolved: [atom()],
  subject: struct() | nil,
  updates: map()
}
```

# `digest`

```elixir
@spec digest(module(), :create | :patch, map(), map()) :: String.t()
```

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.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
