AshOnetime.Transaction (ash_onetime v1.6.0)

Copy Markdown View Source

Transaction-owned idempotency and one-time nonce admission.

This boundary is for hosts that already own one authoritative Ecto transaction and need ash_onetime admission to commit or roll back with the host's effect. It never starts or commits a transaction. Admission and completion require an existing PostgreSQL READ COMMITTED transaction. The stored nonce deadline read can also run outside one.

:operation is a local-code {module, action} pair. :partition, :scope, and :key are exact bounded UTF-8 binaries. A logical partition isolates otherwise identical locators in one store installation without importing a host application's tenant or actor types.

The locator passed to nonce_retention_deadline/2 is a capability. The host must authorize the caller's access to the operation, partition, scope, key, and schema; this module does not perform actor authorization.

Summary

Types

Opaque fresh idempotency admission returned by idempotency/2.

Functions

Returns the claim UUID of a fresh admission.

Completes a fresh idempotency admission with exact response bytes.

Reserves an idempotency key inside the caller's current transaction.

Reserves a one-time nonce inside the caller's current transaction.

Reads a nonce claim's stored retention deadline without reserving or extending it.

Types

admission()

@opaque admission()

Opaque fresh idempotency admission returned by idempotency/2.

Functions

claim_id(admission)

@spec claim_id(admission()) :: Ecto.UUID.t()

Returns the claim UUID of a fresh admission.

Admission is opaque by contract; this is the one sanctioned accessor. The UUID is the durable address of the claim inside the store — persisting it lets a host correlate its own invocation records with the claim (and hand the unchanged UUID to an external execute/recover peer). A pure read: no process, transaction, or store interaction.

complete(admission, payload)

@spec complete(admission(), binary()) :: :ok | {:error, AshOnetime.Error.t()}

Completes a fresh idempotency admission with exact response bytes.

Completion must run in the same process and caller-owned transaction as idempotency/2. The codec selected during admission, the SHA-256 digest, and the exact bytes are bound atomically to the claim.

idempotency(repo, options)

@spec idempotency(Ecto.Repo.t(), keyword()) ::
  {:execute, admission()} | {:replay, binary()} | {:error, AshOnetime.Error.t()}

Reserves an idempotency key inside the caller's current transaction.

Returns {:execute, admission} for a new request, {:replay, exact_bytes} for a completed matching request, or a typed AshOnetime.Error. Reusing the locator with a different fingerprint returns :key_reused_with_different_request; an incomplete matching request returns :request_in_progress.

nonce(repo, options)

@spec nonce(Ecto.Repo.t(), keyword()) :: :ok | {:error, AshOnetime.Error.t()}

Reserves a one-time nonce inside the caller's current transaction.

:verified must contain trusted AshOnetime.Verified facts whose exact key equals :key. A collision returns the typed :nonce_already_used error. No response payload is created.

:max_age and :clock_skew bound acceptance in seconds. Optional :retain_for keeps the claim longer without accepting older proofs; it must be an integer at least :max_age, with retain_for + clock_skew <= 2_147_483_647. Omitting it preserves the original acceptance, retention, and error behavior. With extended retention, a verified late replay returns :nonce_already_used through the stored deadline, inclusive, and :invalid_nonce_window after it regardless of cleanup timing.

nonce_retention_deadline(repo, options)

@spec nonce_retention_deadline(Ecto.Repo.t(), keyword()) ::
  {:ok, DateTime.t()} | :not_found | {:error, AshOnetime.Error.t()}

Reads a nonce claim's stored retention deadline without reserving or extending it.

Supply the same :operation, :partition, :scope, :key, and optional :prefix used by nonce/2. Returns {:ok, datetime}, :not_found, or a typed error. No transaction is required; inside a transaction it observes that transaction's writes. The query uses a savepoint so a query error preserves the caller's transaction. The deadline includes clock skew and the cleanup safety margin. Cleanup is eligible strictly after it; the row may remain longer until cleanup runs. This read is an observation, never permission to execute an effect. The locator is a capability that the host must authorize before calling; no actor authorization runs here.