# Guarantees

URL: https://docs.domainruntime.dev/docs/data/guarantees
Status: Verified
Reviewed: 2026-09-23



<div className="docs-note">
  Every guarantee on this page holds in production today. Examples use the planned 

  `runtime.tx`

   and 

  `runtime.query`

  ; today the same guarantees apply to 

  `await runtime.db()`

   with 

  `db.transact`

   and 

  `db.query`

  .
</div>

This page is the contract. If your code relies on something not listed here, it relies on luck.

## At a glance [#at-a-glance]

|                  | Guaranteed                                                                | Not guaranteed                                   |
| ---------------- | ------------------------------------------------------------------------- | ------------------------------------------------ |
| One `runtime.tx` | all its operations apply, or none do                                      | isolation from concurrent reads                  |
| One action       | input and output validated; runs once per request id; caller recorded     | that its separate `tx` calls are atomic together |
| `.unique()`      | enforced by the database on every write                                   | —                                                |
| Permissions      | every read and write of a production action runs under the caller's rules | —                                                |
| Live queries     | every commit, by any writer, refreshes affected subscribers               | ordering between two different queries           |
| Workflow steps   | a finished step never runs again                                          | that a running step runs only once               |

## A `tx` is the transaction [#a-tx-is-the-transaction]

```ts
await runtime.tx((tx) => [
  tx.accounts_account[from].update({ balance: fromBalance - amount }),
  tx.accounts_account[to].update({ balance: toBalance + amount }),
]);
```

Both updates commit together or not at all. Two `runtime.tx` calls are two transactions: if the second fails, the first stays. Inside one `tx`, if two operations set the same attribute of the same object, the last one wins.

## Reading does not lock [#reading-does-not-lock]

`runtime.query` reads committed data; it does not hold it. Between your read and your `tx`, another action can change it. A check-then-write is only correct if the database can enforce the condition itself:

```ts
// ❌ two concurrent calls can both see "none" and both create
const { orders_customer } = await runtime.query({
  orders_customer: { $: { where: { email: input.email } } },
});
if (orders_customer.length === 0) {
  await runtime.tx((tx) => tx.orders_customer[id()].update({ email: input.email, name: input.name }));
}

// ✅ `email` is .unique(); this creates or updates the one customer
await runtime.tx((tx) =>
  tx.orders_customer[lookup("email", input.email)].update({ name: input.name }),
);
```

For counters and balances, prefer appending over overwriting: record each movement as its own object and derive the total with a query.

<div className="docs-note">
  **Planned.**

   Conditional writes — a 

  `tx`

   that commits only if what you read has not changed — are designed, not built.
</div>

## An action runs once per request [#an-action-runs-once-per-request]

|                                     |                                                                                |
| ----------------------------------- | ------------------------------------------------------------------------------ |
| Same request id, same input         | the first execution is returned; your code does not run again                  |
| Same request id, different input    | `REQUEST_CONFLICT`                                                             |
| Your code throws                    | the execution fails with `action_failed`; committed `tx` calls stay            |
| Your code returns an invalid output | the execution fails with `execution_output_invalid`; committed `tx` calls stay |
| The process running it dies         | the execution is marked failed, not re-run                                     |

A failed execution may have done part of its work. Keep each action's writes in one `tx`, and make a retry with a new request id harmless with caller-chosen ids or unique attributes.

## Permissions apply inside actions [#permissions-apply-inside-actions]

Reads and writes inside an action run with the caller's identity, in development and in production: a user's action cannot read or write what that user could not. A write the rules forbid fails the execution with `action_failed`; writes committed before it stay. Only a service key bypasses rules. See [Permissions](/docs/auth/permissions).

## Live queries follow commits [#live-queries-follow-commits]

Subscriptions refresh from the database's change log. A commit by an action, a workflow step or any other writer refreshes every affected query, in every tab, for every user who may see the result.

## Workflows [#workflows]

A finished step is saved and never runs again. A step that was running when the process stopped runs again, so steps run at least once. A run keeps the identity it was admitted with and the release it started on. See [Workflows](/docs/data/workflows).

## Stale runners cannot write [#stale-runners-cannot-write]

Each execution attempt carries a fence. If an attempt is superseded — its 30-second lease expired and another took over — writes from the old one are rejected.
