# `AshOnetime.Transaction`
[🔗](https://github.com/baselabs/ash_onetime/blob/v1.6.0/lib/ash_onetime/transaction.ex#L1)

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.

# `admission`

```elixir
@opaque admission()
```

Opaque fresh idempotency admission returned by `idempotency/2`.

# `claim_id`

```elixir
@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`

```elixir
@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`

```elixir
@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`

```elixir
@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`

```elixir
@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.

---

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