Accrue.Processor.Idempotency (accrue v0.2.0)

Copy Markdown View Source

Deterministic idempotency key and subject UUID derivation for Phase 3 processor operations (D3-60, D3-61, D3-64).

Same (op, subject_id, operation_id, sequence) tuple → same key → retries converge without side channel. Unlike Accrue.Processor.Stripe.compute_idempotency_key/3 (which produces the short accr_<22b64> form for Stripe's Idempotency-Key HTTP header, D2-11), this module exposes:

  • key/4 — deterministic 64-hex-char SHA256 key prefixed with the op name. Used by Wave 2 billing context functions that need to derive a stable idempotency token from (op, subject_id, operation_id, sequence) before calling the processor (D3-60).
  • subject_uuid/2 — deterministic Ecto.UUID-shape string derived from (op, operation_id). Used when the caller needs a stable v4-shaped UUID for a new primary key before the processor call commits (D3-61).

Examples

iex> Accrue.Processor.Idempotency.key(:create_subscription, "sub_123", "op_abc")
"create_subscription_" <> _

iex> uuid = Accrue.Processor.Idempotency.subject_uuid(:create_subscription, "op_abc")
iex> {:ok, _} = Ecto.UUID.cast(uuid)

Security

SHA256 over a canonical op|subject|operation|seq tuple; collision probability is negligible. The key is NOT secret — it is safe to log — but it MUST NOT be used as an authorization token.

Summary

Functions

Derives a deterministic idempotency key from (op, subject_id, operation_id, sequence).

Derives a deterministic Ecto.UUID v4-shaped string from (op, operation_id). Useful when a caller needs to pre-allocate a stable primary key before the processor call commits (D3-61).

Types

op()

@type op() :: atom() | String.t()

Functions

key(op, subject_id, operation_id, sequence \\ 0)

@spec key(op(), String.t(), String.t(), non_neg_integer()) :: String.t()

Derives a deterministic idempotency key from (op, subject_id, operation_id, sequence).

Output format: "#{op}_#{sha256_hex}" where sha256_hex is the 64-character lowercase hex encoding of SHA256("#{op}|#{subject_id}|#{operation_id}|#{sequence}").

subject_uuid(op, operation_id)

@spec subject_uuid(op(), String.t()) :: String.t()

Derives a deterministic Ecto.UUID v4-shaped string from (op, operation_id). Useful when a caller needs to pre-allocate a stable primary key before the processor call commits (D3-61).

The result passes Ecto.UUID.cast/1 and has the v4 version nibble and RFC 4122 variant bits set so it is spec-conformant.