X402.AuthCapture.Journal (X402 v0.9.0)

Copy Markdown View Source

Durable signer-scope fencing for auth-capture transactions.

Each scope has at most one active transaction. Its immutable intent is reserved before signing; raw bytes and their locally computed hash are persisted before dispatch. dispatch/3 grants permission to send exactly once. A timeout or crash after that marker requires receipt reconciliation, never another send, even if the first caller may not have reached the node.

Confirmed or reverted receipts retain the scope until the application has durably applied their local effects and calls acknowledge/3. Completed records remain as replay tombstones. The configurable history limit fails closed instead of evicting them. Migration or compaction requires an application-owned replay archive, not deleting live journal records. Each history entry has its own key. The small scope record and the selected entry are changed in one atomic multi-key transaction; unrelated history is neither fetched nor rewritten. Existing-operation lookups use read-only snapshots.

Only a preparation with no frozen transaction may be cancelled. Reserving that cancelled intent again issues a fresh owner token. There are no leases or timeout takeovers; abandoned preparations require explicit application recovery. This module coordinates trusted execution code, not untrusted HTTP clients: do not expose owner tokens or accept caller-supplied receipts.

A multi-transaction lifecycle uses one journal record per leg and a separate durable payment record. Releasing the signer scope does not release that payment's admission or authorize repeating a handler.

This namespace does not coordinate other schemes or the existing nonce manager. Use a dedicated gas account or an external coordinator covering every writer. Structural record checks fail closed on detectable corruption; they cannot recover deleted data or authenticate a malicious storage adapter.

Summary

Functions

Releases a completed scope only after its local effects are durably applied.

Reads the active record, including finished work awaiting effect acknowledgement.

Cancels only unprepared work. Frozen or dispatched work cannot be cancelled.

Durably grants the sole permission to send the frozen transaction.

Reads a retained operation without treating a store failure as absence.

Records a trusted, verified receipt without releasing the signer scope.

Builds a journal bound to a concrete EVM network and gas-account address.

Freezes raw transaction bytes and their local keccak-256 hash before any send.

Reserves an immutable intent or returns its existing record.

Types

entry()

@type entry() :: %{
  id: binary(),
  owner: binary(),
  intent: map(),
  phase:
    :preparing
    | :prepared
    | :dispatched
    | :confirmed
    | :reverted
    | :succeeded
    | :failed
    | :cancelled,
  transaction: %{raw: binary(), hash: String.t()} | nil,
  receipt: map() | nil
}

t()

@type t() :: %X402.AuthCapture.Journal{
  history_limit: pos_integer(),
  scope: tuple(),
  store: X402.AuthCapture.Store.t()
}

Functions

acknowledge(journal, id, owner)

(since 0.9.0)
@spec acknowledge(t(), binary(), binary()) :: {:ok, entry()} | {:error, term()}

Releases a completed scope only after its local effects are durably applied.

active(journal)

(since 0.9.0)
@spec active(t()) :: {:ok, entry() | nil} | {:error, term()}

Reads the active record, including finished work awaiting effect acknowledgement.

Returns :journal_changed if ownership changes between selecting the key and reading the atomic snapshot. That is a safe read retry, not a new reservation.

cancel_preparation(journal, id, owner)

(since 0.9.0)
@spec cancel_preparation(t(), binary(), binary()) :: {:ok, entry()} | {:error, term()}

Cancels only unprepared work. Frozen or dispatched work cannot be cancelled.

dispatch(journal, id, owner)

(since 0.9.0)
@spec dispatch(t(), binary(), binary()) :: {:ok, entry()} | {:error, term()}

Durably grants the sole permission to send the frozen transaction.

fetch(journal, id)

(since 0.9.0)
@spec fetch(t(), binary()) :: {:ok, entry() | nil} | {:error, term()}

Reads a retained operation without treating a store failure as absence.

finish(journal, id, owner, phase, receipt)

(since 0.9.0)
@spec finish(t(), binary(), binary(), :confirmed | :reverted, map()) ::
  {:ok, entry()} | {:error, term()}

Records a trusted, verified receipt without releasing the signer scope.

The execution layer must establish transaction identity, finality, success or revert status, and expected events before calling this function.

new(opts)

(since 0.9.0)
@spec new(keyword()) :: {:ok, t()} | {:error, term()}

Builds a journal bound to a concrete EVM network and gas-account address.

prepare(journal, id, owner, raw)

(since 0.9.0)
@spec prepare(t(), binary(), binary(), binary()) :: {:ok, entry()} | {:error, term()}

Freezes raw transaction bytes and their local keccak-256 hash before any send.

reserve(journal, id, intent)

(since 0.9.0)
@spec reserve(t(), binary(), map()) ::
  {:ok, {:reserved | :existing, entry()}} | {:error, term()}

Reserves an immutable intent or returns its existing record.

Only {:reserved, entry} grants a new ownership token. An existing record must be reconciled, not executed again. IDs are nonempty binaries of at most 128 bytes; the caller must derive them from the canonical payment operation.