An Ash resource extension for the attestation record store — the
Ash-idiomatic storage pattern (as ash_authentication's token resource): the
HOST defines the resource (choosing repo, table, domain, policies), this
extension stamps the required shape onto it, and the library reaches it
through config :reactive_dag, attestation_resource: MyApp.Attestation.Record.
defmodule MyApp.Attestation.Record do
use Ash.Resource,
domain: MyApp.Attestations,
data_layer: AshPostgres.DataLayer,
extensions: [ReactiveDag.Attestation.Record]
postgres do
table "attestation_records"
repo MyApp.Repo
end
attestation_record do
# OPTIONAL: derive the signer from the Ash actor. When set and an
# actor is present, `who` is FORCED from it — impersonation becomes
# structurally impossible at the write, not merely discounted at
# read time by the eligibility check.
who_from_actor fn actor -> to_string(actor.email) end
end
endWhat the extension stamps (each only if the host hasn't declared it):
- the record attributes —
cell_id / scope_kind / scope / who / polarity / reason / basis / basis_version / signed_at / metaunder a UUID pk; - a
:signcreate action accepting them, carrying the change that applieswho_from_actorand rejects a reasonless rejection; - a primary
:read.
Being the host's resource, everything Ash composes onto it: policies
(signing authorization in the same framework as the rest of the app),
notifications (pub_sub a signing straight into a refresh), and
generated migrations (mix ash.codegen) instead of a hand-written
expected-shape blob.
Append-only is enforced, not conventional
A verifier REJECTS the resource at compile time if it declares any update or
destroy action. Records are immutable history — a signer's current stance is
their latest record, and whether it counts is computed at read time
(ReactiveDag.Attestation.Evaluation); nothing about a record is ever
edited. The audit trail is the point.
Summary
Functions
The host-configured who_from_actor function, or nil.