Docs / State & recovery
Persist rounds and payment journals
Choose atomic storage semantics that survive concurrent writers and process restarts.
RoundStore
type RoundStore = {
read(roundId: string): Promise<RoundTranscript | undefined>;
compareAndSet(next: RoundTranscript, expectedRevision: number | null): Promise<boolean>;
};The revision is the number of accepted events. null means “insert only if absent.” A non-null revision means “update only if the stored event count still matches.” Do not implement this as a separate read followed by an unconditional write.
const store = sqliteRoundStore("./rounds.sqlite");
await openStoredRound(spec, store);
const result = await submitStoredRoundEvent(spec.id, {
type: "demand", buyerId: authenticatedBuyerId,
request: { quantity: "2", budget: "10" },
}, store);
if (result.status === "conflict") {
// Read the current round, recheck its phase, then decide whether to retry.
}The complete SQLite adapter uses an atomic conditional UPDATE, WAL and FULL synchronization. Node 22.18’s built-in SQLite API is experimental. Applications may implement the same interface with another database.
SettlementJournal
read retrieves a payment entry. reserve atomically inserts a candidate if absent and returns the existing winner otherwise. The entry contains the payment and signed bytes/hash. bindPlan atomically stores or matches the complete plan fingerprint. Split settlement requires bindPlan; its absence is an error.
const journal = {
read: async scope => loadEntry(scope),
reserve: async (scope, candidate) => insertOrReadAtomically(scope, candidate),
bindPlan: async (scope, fingerprint) => insertOrCompareAtomically(scope, fingerprint),
};The snippet states the required adapter contract; loadEntry and the two atomic functions are your database operations, not SDK exports. The reference market contains implemented IndexedDB and SQLite stores and a durable simulation journal. Its Intents CLI contains a separate file journal for live execution.
Order of durability
- Freeze the allocation and bind the payment plan.
- Authorize, prepare the transaction and durably reserve its signed bytes.
- Inspect the stored hash, then broadcast only those bytes if allowed.
- Record finalized receipts and apply application ledger changes once per payment ID.
A transport exception must not cause you to discard the journal and prepare a replacement. A pending transaction can already have been broadcast. Reconcile it first. Independent payments can settle partially.
Storage ownership
Keep one trusted storage namespace per environment. Protect journals like credentials: their signed bytes may still be broadcastable. Browser simulation storage is suitable for synthetic activity, not for custody of live keys. Use unique round IDs and retain the journal for recovery.