> ## Documentation Index
> Fetch the complete documentation index at: https://docs.bijection.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Walkthrough: From a Source System to an Operation

> Connect an external system, relate its records to your own, define a business object, protect it, change it through an operation and follow the outcome

This walkthrough builds one small, complete slice of a Bijection app. A CRM
holds customer accounts. Your app keeps its own customers, relates CRM accounts
to them, shows each customer as one business object, lets permitted staff rename
an account, and follows that rename until the CRM confirms it.

Along the way you meet each idea that sets Bijection apart from an ordinary app
backend:

| Step | You declare | Bijection enforces |
| - | - | - |
| [1. Describe the source](#1-describe-the-source) | The CRM's requests, responses and commands | Every record it publishes is backed by a retained provider response |
| [2. Declare who owns which facts](#2-declare-who-owns-which-facts) | A synced table, a local table and a link type | Only the sync writes CRM data; link cardinality holds on every write |
| [3. Define the business object](#3-define-the-business-object) | A keyed `Customer` view | One meaning, read like a table, reactive in queries |
| [4. Protect it](#4-protect-it) | Read and disclosure rules | Every read and every copy is decided, whichever function makes it |
| [5. Connect the account](#5-connect-the-account) | A connection per deployment | Credentials never reach your code |
| [6. Change it with an operation](#6-change-it-through-an-operation) | `operation` with an external call | Local change and external intent commit together |
| [7. Follow the outcome](#7-follow-the-outcome) | Nothing new | Local acceptance, provider confirmation and publication stay distinct |

The code uses only the functions you already know: queries, mutations and
`ctx.db`. What changes is that ownership, relationships, rules and external
effects are declared once and enforced by the backend.

> [!NOTE]
>
> The walkthrough assumes you have [installed the CLI](https://docs.bijection.com/get-started/install.md) and
> have a project running with `bijection dev`. The CRM in it is illustrative:
> point the connection at a provider whose API actually offers the guarantees
> you declare.

## 1. Describe the source

An [integration](https://docs.bijection.com/integrations/overview) states, in TypeScript, what Bijection
needs to know about one external system: the requests it may make, what each
response proves, which collections it reads and which commands it may send back.

```ts bijection/crm.ts theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
import { makeFunctionReference } from "bijection/server";
import { defineIntegration } from "bijection/connectors";
import { v } from "bijection/values";

export const crm = defineIntegration({
  requests: {
    // Request contracts: `identity`, `changes`, `renameSend`, `renameProbe`.
    …
  },
  async identify(ctx) { … },
  collections: {
    accounts: {
      schema: { external_id: v.string(), name: v.string() },
      protocol: {
        kind: "change_feed",
        position: { domain: "account_changes", encoding: "uint64_decimal" },
        checkpoint: {
          initial: "0",
          continuity: "complete_prefix",
          retention: { kind: "stated", unit: "days", value: 90 },
        },
        records: "full_or_delete",
        atomic_group: "one_change",
      },
      sync: { every: { minutes: 5 }, async read(ctx, input) { … } },
    },
  },
  commands: {
    rename: {
      target: "accounts",
      args: { name: v.string() },
      governingRule: {
        query: makeFunctionReference<"query", any, [null]>("access:renameRule"),
        reads: ["grants", "crm_accounts"],
      },
      delivery: {
        kind: "idempotent",
        retention: { kind: "stated", unit: "days", value: 90 },
        max_delivery_hours: 1,
        request_expiry: "provider_enforced",
      },
      condition: {
        kind: "target_version",
        from: { kind: "record_observation_position" },
      },
      async send(ctx) { … },
      async reconcile(ctx) { … },
    },
  },
});
```

[Defining integrations](https://docs.bijection.com/integrations/defining-integrations.md) shows the complete
request contracts and handlers, including a
[command](https://docs.bijection.com/integrations/defining-integrations.md#commands). Three things matter
here:

* **The protocol states what the provider guarantees.** This CRM offers an
  ordered change feed that it retains for 90 days. Bijection decides what a sync
  may publish from that declaration, so it has to match the provider's
  documentation. A plain paged listing declares a weaker protocol, and declaring
  more cannot make the provider offer it.
* **The command states how a change is delivered and proved.** `idempotent` says
  the CRM deduplicates requests by their identity for 90 days, so a lost
  response can be retried under the same identity. `condition` says the CRM
  checks the record version the request was based on. `reconcile` is how
  Bijection later asks the CRM what became of a request. `governingRule` names
  the query that decides who may have the CRM make this change; every command
  declares one, and you write it in [step 4](#4-protect-it).
* **No address or credential appears.** They belong to the connection you create
  in [step 5](#5-connect-the-account), so the same definition runs against a
  test account and a production account.

## 2. Declare who owns which facts

A Bijection schema says who is allowed to write each table. Three kinds of
tables appear here:

```ts bijection/schema.ts theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
import { defineSyncedTable, defineSchema, defineTable } from "bijection/server";
import { v } from "bijection/values";
import { crm } from "./crm";

// CRM-owned: only the sync writes these rows.
export const crm_accounts = defineSyncedTable({ source: crm.accounts }).index(
  "by_external_id",
  ["external_id"],
);

// App-owned: your mutations write these rows.
export const customers = defineTable({
  label: v.string(),
  note: v.string(),
  revision: v.int64(),
});

// App-owned association between the two, with enforced cardinality.
export const customer_accounts = defineTable({
  account_id: v.id("crm_accounts"),
  customer_id: v.id("customers"),
})
  .index("by_account", ["account_id"])
  .link(
    { account_id: [0, 1], customer_id: [0, "many"] },
    { customer_id: { opposite: "accounts" } },
  );

export default defineSchema({ crm_accounts, customers, customer_accounts });
```

* `crm_accounts` is a [synced table](https://docs.bijection.com/integrations/synced-tables.md). Receiving the
  CRM's data does not make your app its owner: the generated types leave it out
  of `ctx.db`'s write methods, and a write that reaches the backend anyway fails
  with a `ReadOnlySource` error,
  `crm_accounts accepts changes only through its admitted source`. To change an
  account, you ask the CRM, as in [step 6](#6-change-it-through-an-operation).
* `customers` holds facts your app owns, such as an internal label and a note.
* `customer_accounts` is a [link type](https://docs.bijection.com/views/links.md). Each CRM account belongs
  to at most one customer (`[0, 1]`), and a customer can have any number of
  accounts. Bijection refuses any transaction whose final state breaks those
  bounds, whichever mutation makes it. `opposite: "accounts"` names the
  navigation from a customer to its linked accounts, used in
  [step 6](#6-change-it-through-an-operation).

Relating accounts to customers is an ordinary mutation:

```ts bijection/customers.ts theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
import { mutation } from "./_generated/server";
import { v } from "bijection/values";

export const attach = mutation({
  args: { account_id: v.id("crm_accounts"), customer_id: v.id("customers") },
  handler: async (ctx, args) => {
    await ctx.db.insert("customer_accounts", args);
  },
});
```

Attaching an account that already belongs to another customer is refused because
of the link's bound. You don't write that check.

## 3. Define the business object

Your app wants to show a *customer*: its label, its note and how many CRM
accounts it has. Instead of assembling that in every query, declare it once as a
[view](https://docs.bijection.com/views/overview):

```ts bijection/schema.ts theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
import {
  defineSyncedTable,
  defineSchema,
  defineTable,
  defineView,
  q,
} from "bijection/server";

// …the tables from step 2…

export const Customer = defineView({
  relation: q
    .from(customers)
    .as("customer")
    .aggregate("accounts", {
      from: q.from(crm_accounts),
      via: ({ customer }) => customer.accounts,
      measures: (accounts) => ({ account_count: accounts.count() }),
    })
    .select(({ customer, accounts }) => ({
      label: customer.label,
      note: customer.note,
      revision: customer.revision,
      account_count: accounts.account_count,
    })),
});

export default defineSchema({
  crm_accounts,
  customers,
  customer_accounts,
  Customer,
});
```

The view defines the `Customer` object type. Each callback receives the typed
fields of the table it reads, so a misspelled field fails to type-check. Each
row is keyed by the customer's `_id`, and its type is inferred from the
expression. Queries read it with the same `ctx.db` calls as a table:

```ts bijection/customers.ts theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
import { query } from "./_generated/server";

export const list = query({
  args: {},
  handler: async (ctx) => {
    return await ctx.db.query("Customer").take(50);
  },
});
```

A client subscribed to `list` receives a new result when an account is attached
or a note changes, exactly as with any other [reactive query](https://docs.bijection.com/realtime.md).

`via` follows the link's `accounts` navigation, so you don't name the join keys.
It counts the CRM accounts the customer is linked to that the CRM still has; a
customer with no accounts counts `0`.

You never write to a view. By default it is evaluated when read; declaring
`storage: { maintenance: "transactional" }` stores its output and keeps it
current in the same transaction as every write to its inputs. Both forms return
the same rows. See [Materialized views](https://docs.bijection.com/views/materialized-views.md).

## 4. Protect it

In an ordinary backend, each function checks the caller before it reads.
Bijection lets you attach that decision to the data instead: an
[access model](https://docs.bijection.com/access/access-model.md) declares who may do what once, and
Bijection enforces it on every read and write of the tables it protects, from
any function.

Before you connect the account in step 5, initialize access:

```sh theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
bijection access init
```

It signs you in to your app and makes you its first owner, which it can do only
while the protected tables are empty. It installs the application access
endpoints and the protected `assignments` table in your schema. Declare your
resources and roles in `access.ts`:

```ts bijection/access.ts theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
import {
  defineAccess,
  createAccessReader,
  principalReference,
  bothAccessResults,
} from "bijection/server";
import type { Id } from "./_generated/dataModel";
import { internalQuery } from "./_generated/server";
import { requireRenameController, senderCalls } from "./deliveries";
import { crm } from "./crm";

export const access = defineAccess({
  initialOwner: { environment: "BIJECTION_INITIAL_OWNER" },
  resources: {
    root: { recovery: { role: "root:owner" } },
    customer: { permissions: ["rename"] },
    customerAccount: { parent: "customer", permissions: ["rename"] },
    account: { permissions: ["link", "rename"] },
    operation: { permissions: ["read", "invoke", "preview"] },
  },
  components: ["operations", "providerDelivery"].map((component) => ({
    request: { component, custody: true as const, effects: {} },
    from: ["customer", "customerAccount", "account"],
    custody: true,
  })),
  roles: {
    "root:owner": [
      "root:*",
      "customer:*",
      "customerAccount:*",
      "account:*",
      "operation:*",
    ],
    "root:staff": [
      "customer:read",
      "customer:create",
      "customer:edit",
      "customer:rename",
      "customerAccount:read",
      "customerAccount:create",
      "customerAccount:rename",
      "account:read",
      "account:link",
      "account:rename",
    ],
    "operation:invoker": ["operation:read", "operation:invoke"],
    "operation:viewer": ["operation:read"],
  },
  releases: {
    // Linking an account to a customer copies the account's ID into the link.
    link: {
      from: ["account"],
      to: { table: "customer_accounts" },
      requires: "link",
    },
    // A rename notes it on the customer, having read its links and account.
    note: {
      from: ["customerAccount", "account"],
      to: { table: "customers" },
      requires: "rename",
    },
    // A rename may carry the customer, its links and the CRM account it
    // read to the CRM, when its requester holds `rename` on them.
    rename: {
      from: ["customer", "customerAccount", "account"],
      to: { command: crm.commands.rename },
      requires: "rename",
    },
  },
});

// Who may have the CRM rename an account: whoever holds `rename` on it.
// Asked when a request is accepted and again before each send.
export const renameRule = internalQuery({
  policy: { reads: () => access.inputs(), calls: senderCalls },
  handler: async (ctx, { target }: { target: Id<"crm_accounts"> }) => {
    const controller = await requireRenameController(ctx);
    const permission = await createAccessReader(
      access,
      ctx,
    ).requirePrincipalPermission({
      principal: principalReference(
        controller.requester_person ?? controller.requester,
      ),
      permission: "account:rename",
      resource: { type: "account", key: target },
    });
    return [
      bothAccessResults(permission, { validUntil: controller.expires_at }),
    ];
  },
});

declare const process: { env: Record<string, string | undefined> };
```

The delivery gateway's `requireRenameController` checks the actual retained
program and its original Operations admission or receipt. Its `senderCalls`
declares those exact query dependencies. It returns the original requester's
identity with a finite deadline; it does not authenticate as that requester. The
current `account:rename` check above still decides permission. Configure that
gateway as in
[External calls](https://docs.bijection.com/operations/external-calls.md#install-the-delivery-owner).

Bind each document and field to its resource permissions in the schema:

```ts bijection/schema.ts theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
import { access } from "./access";

// Append these bindings to the declarations from steps 2 and 3.
const readOnly = { read: "read", create: false, update: false } as const;
const editable = { read: "read", create: "create", update: "edit" } as const;
const document = { read: "read", create: "create", delete: "delete" } as const;

// crm_accounts = defineSyncedTable({ source: crm.accounts }).index(…)
//   .access(access.resources.account, {
//     document: { read: "read", create: false, delete: false },
//     fields: { external_id: readOnly, name: readOnly },
//   });
// customers = defineTable(…).access(access.resources.customer, {
//   document, fields: { label: editable, note: editable, revision: editable },
// });
// customer_accounts = defineTable(…).index("by_customer", ["customer_id"]).link(…)
//   .access(access.resources.customerAccount, {
//     parent: { field: "customer_id", index: "by_customer" },
//     document, fields: { account_id: editable, customer_id: editable },
//   });
// defineSchema({ …, assignments: access.assignments() }, { access })
```

A link belongs to its customer (`parent`), so a role on a customer covers its
links. The `Customer` view needs no binding: reading it reads its inputs, and
their rules decide.

Three separate decisions are at work:

* **Reading.** A role's `read` permissions decide who may see the rows. A
  function called by anyone else fails with `Read access was refused`, whether
  it is a public query, an internal function or a new query someone adds next
  month.
* **Releasing.** Once a function has read protected rows, anything it writes or
  sends may carry that data elsewhere. Rows may flow into their own table and
  their children's; anywhere else needs a declared release. `link` lets an
  account's ID into a link, `note` lets a rename record a note on its customer,
  and `rename` lets the rename in step 6 send what it read to the CRM. A release
  to a command is decided for the request's requester when it is accepted and
  again before each send. Anything else is refused, and the function log names
  what was read and where it went.
* **Commanding.** `renameRule`, the command's `governingRule` from step 1,
  decides whose requests the CRM may receive: whoever holds `rename` on the
  account. It reads the grants as a private input, and refuses by throwing.

Decisions are reactive: a grant or a revocation reruns the subscriptions it
affects. [Access model](https://docs.bijection.com/access/access-model.md) covers roles, sharing and releases
in full.

## 5. Connect the account

Push the code (`bijection dev` does it for you). The synced table now exists,
empty. A deployment administrator connects it to a real CRM account:

```sh theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
bijection connection credential create CRM_CREDENTIAL --from-file crm-credential.json
bijection connection configure crm --integration crm:crm \
  --base-url https://crm.example.com/api --credential-ref CRM_CREDENTIAL
bijection source connect crm_accounts --connection crm --wait 30s
bijection source status crm_accounts
bijection source refresh crm_accounts --wait 30s
bijection source acquisitions crm_accounts
```

The credential is read from a file, stored privately in the deployment and used
only by the host for the requests the definition declares. Your functions never
see it. `identify` records which CRM account the connection speaks for, and
every later sync is checked against that identity. `source connect` retains
verification and installation work; closing the CLI leaves accepted setup
running. The wait observes the first publication within its deadline. After
connection, a sync of the `accounts` collection is requested every five minutes
and `crm_accounts` fills in. The schedule requests work; it does not promise a
completed sync every five minutes while the CRM is unavailable. See
[Connecting and syncing](https://docs.bijection.com/integrations/connections.md).

When a private credential changes, repair the same connection:

```sh theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
bijection connection credential set crm --from-file crm-credential.json --wait 30s
```

The setup owner verifies the same CRM identity and recovers affected reads while
keeping published facts. If the submission reply is lost, use
`bijection connection credential recover crm`; it reads the original repair and
never resends the secret.

A synced table reports what the source last proved, not the CRM's state at this
instant. `source status` separates published coverage from current update
progress. `source refresh` selects an exact refresh sequence. The native
`source acquisitions` command exposes its finite observation window; the
source-health component owns retained reports and stable history pagination.
[Coverage and freshness](https://docs.bijection.com/integrations/synced-tables.md#coverage-and-freshness)
shows how a query can tell how current it is.

## 6. Change it through an operation

Renaming an account is a business change that has to reach the CRM. It must not
be applied twice when a response is lost, a person may want to preview it first,
and only some users may make it. Declare it as an
[operation](https://docs.bijection.com/operations/overview):

```ts bijection/customers/renameAccount.ts theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
import {
  commandRequest,
  deliveryRef,
} from "@bijectionhq/connections/deliveries";
import { v } from "bijection/values";
import { operations } from "../operation";
import { deliveries } from "../deliveries";
import { crm } from "../crm";

export default operations.define({
  args: {
    customer_id: v.id("customers"),
    account_id: v.id("crm_accounts"),
    name: v.string(),
    expected_revision: v.int64(),
  },
  returns: v.object({ delivery: deliveryRef }),
  policy: {
    invoke: () => ({
      permission: "operation:invoke",
      resource: { type: "operation", key: "customers:renameAccount" },
    }),
    read: () => ({
      permission: "operation:read",
      resource: { type: "operation", key: "customers:renameAccount" },
    }),
  },
  handler: async (ctx, args) => {
    const customer = await ctx.db.get(args.customer_id);
    if (!customer || customer.revision !== args.expected_revision) {
      ctx.refuse(
        "EDIT_CONFLICT",
        "The customer changed. Read it again before making a new request.",
      );
    }
    const linked = await ctx.db
      .related(args.customer_id, "accounts")
      .has(args.account_id);
    if (!linked) {
      ctx.refuse(
        "ACCOUNT_NOT_LINKED",
        "This account does not belong to this customer",
      );
    }
    await ctx.db.patch(args.customer_id, {
      note: `Renamed account to ${args.name}`,
      revision: args.expected_revision + 1n,
    });
    const delivery = await deliveries.submit(ctx, {
      request: commandRequest(crm.commands.rename, {
        target: args.account_id,
        args: { name: args.name },
      }),
    });
    return { delivery };
  },
});
```

Configure `operations` as described in
[Defining operations](https://docs.bijection.com/operations/defining-operations.md), and install the
`deliveries` gateway with its current access and resource policies as shown in
[External calls](https://docs.bijection.com/operations/external-calls.md#install-the-delivery-owner).

Assessment runs the retained handler and discards its trial writes. At
acceptance, the link check, local note, delivery request and Operations receipt
**commit together, or not at all**. Nothing is sent to the CRM inside that
transaction. Connections schedules the accepted delivery afterwards.

* `customer_id` is an ordinary typed argument naming the customer being changed.
* `ctx.db.related(args.customer_id, "accounts").has(args.account_id)` follows
  the link from step 2 and checks the selected account.
* `ctx.refuse` records a business refusal for the original request. An
  unexpected error or authorization failure throws and cannot leave a partial
  acceptance.
* The handler returns the full original delivery reference. Its routing and
  current authorization remain with Connections after deployment changes.

Every caller of an operation needs explicit grants: `invoke` to submit a
request, and `read` to read what it accepted, which includes the receipt
`invoke` returns and the `status` in step 7. As the owner, give a staff member
the `staff` role from step 4 and both permissions on this operation:

```sh theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
bijection access grant root:staff --to 'identity:https://auth.example.com|user_123' --on root
bijection access grant operation:invoker --to 'identity:https://auth.example.com|user_123' \
  --on operation:customers:renameAccount
```

`operation:viewer` grants `read` alone. Revoke a grant with
`bijection access revoke <grant>`; `bijection access list` shows its ID.

That user invokes the operation from your app through its Bijection `client`.
Each request carries a request ID, chosen once when the user decides and kept
with the request, so a lost response is recovered rather than applied twice (see
[Calling operations](https://docs.bijection.com/operations/calling-operations.md)):

```ts src/renameAccount.ts theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
import {
  createOperationClient,
  operationReference,
  OperationInterrupted,
} from "@bijectionhq/operations/browser";
import { api } from "../bijection/_generated/api";

const requests = createOperationClient(client);
const rename = operationReference(api.customers.renameAccount);
try {
  const decision = await requests.run(rename, {
    customer_id: customerId,
    account_id: accountId,
    name: "Northwind Supplies Ltd",
    expected_revision: customer.revision,
  });
  if (decision.kind === "accepted") {
    console.log(decision.receipt.result);
  } else if (decision.kind === "refused") {
    console.log(decision.refusal);
  } else {
    console.log(decision.requirements);
  }
} catch (error) {
  if (!(error instanceof OperationInterrupted)) throw error;
  // Retain this local recovery identity; the original arguments are saved.
  console.log("Check the original request", error.request_id);
}
```

Keep `expected_revision` from the read that opened the edit. Review and retry
keep that same value. Reading a newer revision and silently replacing it would
accept a decision the user never made. Start app-owned customers at `0n`; each
accepted edit increments the revision in the same transaction.

In React, the Operations component retains the draft and original request. A
stable `interactionKey` restores the edit with its original revision:

```tsx theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
import { operationReference } from "@bijectionhq/operations/browser";
import {
  useOperationForm,
  useOperationSnapshot,
} from "@bijectionhq/operations/react";

const rename = operationReference(api.customers.renameAccount);
const form = useOperationForm(rename, {
  interactionKey: `rename:${customer._id}:${account._id}`,
  initialValues: {
    customer_id: customer._id,
    account_id: account._id,
    name: account.name,
    expected_revision: customer.revision,
  },
});
const state = useOperationSnapshot(form);
```

After `state.ready`, bind the typed field's `getSnapshot()` and `edit()`
commands, and submit through `form.handleSubmit`. After a lost reply, use
`form.inspect()` or explicitly `form.resume()` on that original request.
`prepare()` follows immutable admission and its candidate requires explicit
acknowledgement; the old editable native preview companion is retired. See
[Forms](https://docs.bijection.com/operations/forms.md).

> [!NOTE]
>
> The current operator source supports the CLI path below. The owner holds every
> permission, so once [team or staff sign-in](https://docs.bijection.com/auth/team-members.md) is configured
> you can run the operation as yourself. Administration alone gives no business
> authority. Use a CLI built from the same source revision as these examples;
> public release availability may lag the source reference.

```sh theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
bijection operation run customers:renameAccount \
  customer_id=... account_id=... name='Northwind Supplies Ltd' expected_revision=0
```

## 7. Follow the outcome

The Operations receipt proves that the local note and delivery request
committed. Its `result.delivery` is the original Connections reference.
Subscribe to an ordinary query to follow that delivery:

```ts bijection/deliveryStatus.ts theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
import { deliveryRef } from "@bijectionhq/connections/deliveries";
import { query } from "./_generated/server";
import { deliveries } from "./deliveries";

export const get = query({
  args: { reference: deliveryRef },
  handler: (ctx, args) => deliveries.inspect(ctx, args.reference),
});
```

The observations have separate owners:

| Observation | What it establishes |
| - | - |
| Accepted Operations receipt | The local business transaction committed. |
| Delivery `phase` | Connections' progress, including `delivered`, `refused` or `uncertain`, based on authentic provider evidence. |
| Physical `settlement` | Whether the original external attempts are closed. A later success does not close an earlier unanswered attempt. |
| Local `publication` | Whether the delivery's local result has been published. Queries show the renamed account only after a valid response publication or ordinary Source acquisition. |

Operations acceptance stays accepted while delivery progresses. An uncertain
provider outcome is not a failed local transaction. A cached success can prove
the original rename while a newer independent CRM value stays current.

Two responses can be lost:

* **Your app's acceptance response.** Recover through the same retained client
  request and original arguments. An accepted request returns its original
  receipt and full delivery reference. A new request is a new business change.
* **The CRM's response.** Keep the original delivery reference. Connections may
  reconcile through the captured command and, for an idempotent declaration,
  resend the original immutable key and payload within its remaining bounds.
  Cancellation or expiry cannot prove that a possible earlier write did not
  happen.

Observation is reactive and sends no provider request. Explicit cancellation,
reconciliation and continuation revocation use ordinary application mutations
under current policy and the inspected delivery revision. See
[External calls](https://docs.bijection.com/operations/external-calls.md#uncertainty-retries-and-controls).

## Try it on a branch first

A [branch](https://docs.bijection.com/branches/overview) forks the deployment's live data at one exact
moment. Operations you run on it change only that branch. Connections records
its external deliveries in `held` mode, so no provider request is sent.

```sh theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
bijection branch fork rename-trial
bijection operation run customers:renameAccount \
  customer_id=... account_id=... name='Contoso Trial' expected_revision=0 \
  --branch rename-trial
bijection request get <request-ref>
```

The returned request reference retains the original branch generation, inputs
and acceptance. Inspect it under your current permissions to recover the receipt
and follow its held Connections delivery.

For an operation defined with `is_review_required: true`, `operation run`
returns a pending request. Prepare and inspect that original request before
selecting its exact candidate:

```sh theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
bijection operation prepare <request-ref>
bijection request get <request-ref>
bijection request resume <request-ref> --candidate-digest <candidate-digest>
```

Trial writes are discarded during preparation. Acceptance commits the local
change and held delivery together. Retrying keeps the original request and
candidate; editing the requested arguments requires an explicit new request.
Branch program publication has its own review and explicit destination. It does
not replay accepted business requests into live data.

## What you built

* The CRM remains the owner of its accounts. Your app stores only its own facts
  and the relationships it has accepted.
* `Customer` is declared once and read everywhere with ordinary queries.
* Access is attached to the data, and reading is distinct from disclosing.
* A rename is one named business change with a stable identity. Its local effect
  and its external intent commit together, and its external outcome is followed
  until the CRM's evidence settles it.

Everything else in Bijection's function model still applies. Keep using
[queries](https://docs.bijection.com/functions/query-functions.md),
[mutations](https://docs.bijection.com/functions/mutation-functions.md) and [actions](https://docs.bijection.com/functions/actions.md) for
work that owns no external business change.
[Understanding Bijection](https://docs.bijection.com/understanding/overview) explains how the pieces fit
together.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.