Skip to content

Latest commit

 

History

History
152 lines (127 loc) · 7.87 KB

storage.md

File metadata and controls

152 lines (127 loc) · 7.87 KB

Storage

The SQLite file at storage.receipts.path stores the gateway's records across restarts:

  • receipts, the commerce event log, payment attempts and the transaction journal;
  • from 2.6.0, AP2 mandate holds, ACP idempotency claims and A2A x402 tasks waiting for payment;
  • from 2.7.0, payment retry keys and, on a resource with idempotency.redelivery, responses delivered with a client retry key, unencrypted. retentionHours limits reuse; a later claim sweep deletes expired rows (see security.md);
  • a record of each deprecated store file it imported.

The gateway creates the file at 0600, and any missing directory at 0700, and runs it in WAL mode. Run one gateway per database.

Deprecated store files

To upgrade from 2.5, keep the legacy paths set and their files in place. 2.6.0 still accepts both keys, and start imports the files.

In 2.6.0, ACP idempotency records and AP2 mandate holds use the receipt database. The settings for their separate files are deprecated: protocols.acp.idempotency.path and authorization.ap2.replay.path. For an enabled feature, config warns when its key is set. It rejects legacy paths that resolve to the same path as another legacy source or the receipt database, except :memory:. The 3.0.0 plan removes both keys.

agent-commerce start imports the files:

  • Each source is imported once, with its rows and import marker committed together. Later starts skip recorded imports. ACP schema-v1 rows are skipped, matching the 2.5 migration; a nonzero count of claims not marked completed is logged at warn.
  • start locks existing source files against writes until shutdown. While a lock is held, a 2.5 gateway cannot update that file.
  • The first import needs a writable directory. If a read-only mount prevents the import, startup fails with STORAGE_ERROR. Make the directory writable or copy the file somewhere writable and point the key there. After a recorded import, SQLITE_CANTOPEN logs a warning instead; other lock failures still stop startup.
  • With AP2 enabled, reconcile and resolve refuse an existing AP2 file with no recorded import. Run start first; these commands do not import it.
  • reconcile and resolve refuse receipt databases below schema v5 without migrating them. This preserves access for a 2.5 gateway still using the file. Back up the database and run start once to migrate it.
  • Remove the key after doctor confirms the file was imported and no 2.5 gateway still uses it. Changing a recorded source path is refused.

When an enabled feature has no recorded import or prior check, start checks for past use in the receipt database. For AP2, it looks for a receipt or transaction with an authorization; for ACP, it looks for an ACP event. If that evidence exists and the legacy key is unset or names a missing file, startup fails with STORAGE_ERROR.

Point the key at the previous version's file to import it, or at an empty file to accept the loss of its records. :memory: also accepts the loss, since a 2.5 in-memory store retained nothing across restarts. If no file is available and the check passes, start records that result so later starts can omit the key. A file supplied later can still be imported.

agent-commerce doctor opens the receipt file read-only, without migrating its schema or importing legacy records. If the schema is older than v5, such as a 2.5 database, it warns that the next start will migrate it. For each enabled AP2 or ACP feature, it checks legacy settings against recorded imports and past-use evidence. It reports recorded imports and warns when it cannot check whether an existing file was imported. It reports FAIL for a changed source path or an unset key or missing file that fails the past-use check. It also checks for past use in a 2.5 database.

Backup

npm run agent-commerce -- backup --out ./backups/2026-11-01

backup opens sources read-only and can run alongside a live gateway. It does not migrate or import records, or take a write lock on the sources. --config, --env-file and --json work as they do for other commands.

  • --out must name a new or empty directory. To create a new one, its parent must exist; the command creates it at 0700. An existing directory keeps its permissions.
  • It writes receipts.sqlite, a consistent snapshot taken with VACUUM INTO. It refuses a receipt database at a schema other than v4 (2.5) or v5 (2.6). The snapshot includes the database's unencrypted stored responses.
  • For enabled AP2 or ACP, it also copies any configured legacy file that exists, is not :memory: and has no recorded import. The copies are named ap2-replay.sqlite or acp-idempotency.sqlite. Before the first 2.6.0 start, those files hold records absent from the receipt database. Each file is a separate snapshot.
  • Every copy is created at 0600 and must pass PRAGMA integrity_check.
  • backup.json is written last. It records the package version and creation time, plus each copy's source config key, schema version and SHA-256 hash. A directory without the manifest is an incomplete backup.

Exit 0 means the backup is complete; 1 means it is not.

Restore

  1. Stop the gateway, and any reconcile or resolve run.
  2. Check each file against its SHA-256 in backup.json.
  3. Delete the receipt database and its -wal and -shm files: a leftover WAL file belongs to the old database. Copy receipts.sqlite to storage.receipts.path.
  4. Restore any ap2-replay.sqlite or acp-idempotency.sqlite included in the backup to the path named by its config key. Remove the old file and its -wal and -shm files before copying. Keep the key set so the next start imports the restored records.
  5. Start the gateway, then run agent-commerce reconcile.

Restoring a backup discards local records written after its snapshot:

  • Later receipts, events, payment attempts, holds, idempotency claims and pending tasks are lost. Transfers remain on chain.
  • A proof spent after the backup reaches the facilitator again. On EVM, verification rejects the spent authorization. Solana and NEAR rely on the chain rejecting a transaction or nonce it has already processed. If a replay passes verification, the authorization flow runs the backend before settlement is refused, then withholds the response. The proof cannot pay twice, but the backend may run again.
  • An AP2 mandate consumed after the backup can authorize another purchase until it expires. That purchase still requires payment.
  • Retrying an ACP checkout under a key claimed after the backup reaches the merchant again with the same derived Idempotency-Key.
  • Payment retry keys claimed after the snapshot are lost. A fresh proof with such a key follows the normal purchase flow; an exact resend faces the spent-proof checks above.

A transaction recorded as unresolved in the backup still needs reconcile or resolve to establish its outcome.

A 2.5 gateway refuses a receipt database migrated by 2.6.0. To roll back to 2.5, restore a backup made before the upgrade.

Shutdown

On the first SIGTERM or SIGINT, the gateway stops accepting connections and waits for in-flight requests without a time limit. It then stops protocol adapters, closes authorization providers and the database, and exits 0 on success. A shutdown error exits 1.

A second SIGTERM or SIGINT exits immediately with status 1. An orchestrator can also send SIGKILL when its grace period expires, terminating the process without cleanup. Docker Compose's stop_grace_period defaults to 10 s. Either forced exit can leave an unfinished purchase, for example an attempt marked reserved or settlement-uncertain. Replays of those attempts return 409 without a fresh challenge. Run reconcile after restart; use resolve when the available evidence cannot establish the outcome. Set the grace period above your slowest settlement plus backend call.