> ## 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.

# Headless Bulk Operations

> Prepare an exact selection, approve its effects, and let the server finish the work

A bulk job applies one admitted operation to a retained list. The server owns
preparation, scheduling and recovery. The CLI, SDK and dashboard send explicit
controls and observe the same job; closing any client does not pause it.

Each chunk is a separate native operation request with immutable arguments,
retained code and an engine-assigned request identity. The job coordinates these
requests. Native operations continue to own permissions, proposals, approvals,
external calls and reconciliation.

```mermaid theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
flowchart LR
  CLI[CLI] --> Job[Retained list job]
  SDK[SDK] --> Job
  UI[Dashboard] --> Job
  Job --> Wait[Native keyed wait]
  Wait --> Proposal[Exact native proposals]
  Proposal --> Review[All required approvals]
  Review --> Requests[Bounded concurrent requests]
  Requests --> Calls[Durable calls and reconciliation]
  Calls --> Outcomes[Receipts and publication]
  Outcomes --> Job
```

## Admit the operation

The application's `listsApplication` declaration chooses which operations may
run unattended and the permission that native admission must check on **every**
selected object:

```ts theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
const lists = listsApplication(components.lists, {
  ...hostOptions, // The application's directory, access and object readers.
  bulkOperations: {
    "orders:remediate": {
      operation: api.orders.remediate,
      permission: "order:automate",
      selection_field: "order_id",
    },
  },
});
export const {
  prepareRun,
  submitRun,
  approveRun,
  cancelRun,
  recoverRun,
  purgeRun,
  run,
  inspectPlan,
  bulkResults,
  reviewRun,
  bulkProgress,
  chunkExecution,
} = lists;
```

For a batch operation, `selection_field` must name a required native ID field of
the operation's required array of objects. Its catalog `selectionArgument` maps
that field to `_id` and other fields to current object properties. An
object-target operation uses one object per request and omits `selection_field`.

Bind both `permission` and `work` in `listsBinding`. The `work` query follows
`listsWork` from `@bijection/lists/policies`; it explicitly grants or refuses
the installation's native coordinator its metadata reads and bookkeeping writes.
Installing a component does not grant this permission automatically. This
coordinator permission cannot replace the saved business principal's operation
or per-object permissions.

## Prepare, inspect and submit from the SDK

Use a client with durable request storage, such as
[`fileRequests`](/client/javascript/consumer-sdk) for Node. Save the job ID in
your own business record. Supply stable control request IDs when resuming work
across process restarts.

```ts theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
const bulk = client.bulk.using(api.features.lists);
const prepared = await bulk.create(
  {
    list: listId,
    revision: inspectedRevision,
    operation: "orders:remediate",
    args: { reason: "inventory correction" },
    expires_at: Date.now() + 60 * 60 * 1000,
    parallelism: 4,
    on_refusal: "stop",
  },
  { request_id: "inventory-correction/prepare" },
);

if (
  prepared.kind !== "accepted" ||
  prepared.receipt.result.kind !== "available"
) {
  // Inspect or recover this original control request. Do not invent another ID.
  throw new Error("Preparation has not returned an available job reference");
}
const job = bulk.open(prepared.receipt.result.value.run);
const observed = await job.follow({ until: "prepared", timeout_ms: 30_000 });
if (observed.reason === "timeout" || observed.status?.state !== "prepared") {
  // Save job.ref and observe again later. The server continues preparation.
  return;
}
const plan = await job.inspectPlan({ after: null, limit: 5 });
if (!plan.digest) throw new Error("The plan is not sealed yet");
// Inspect every page, following cursor until is_complete. Each proposal includes
// its exact effects and native approval requirements under the current reader.
await job.submit(plan.digest, {
  request_id: "inventory-correction/submit",
});
```

An eligible independent reviewer uses their own authenticated client to call
`job.review(...)` and `job.approve({ plan_digest, after, limit }, options)`.
Approval is paginated and each page is transactional. Follow every returned
cursor. The coordinator waits for all required chunk approvals before
dispatching business requests. Approval never grants standing source access or
replaces the operation's native checks.

`job.inspectPlan({ after, limit })` returns the sealed digest, retained list
revision, operation contract, transaction mode, execution policy, effective
expiry, immutable native review-set reference and one page of exact native
proposals. Follow `cursor` until `is_complete`. The grouping is sealed in native
custody; a child cannot execute with another proposal or outside that grouping.
Each proposal has its own read dependencies. Preparation does not simulate
previous chunks, and the plan does not claim one global business-state snapshot.

`job.status()` observes local job state and separate `local_requests` counts
(total, pending, accepted, refused and withdrawn).
`job.results({ after, limit })` pages through chunk decisions, their selected
object IDs and original request/proposal locators. Object IDs pass through the
reader's current application `getMany`; restricted and absent members are
counted without naming them. A batch decision applies to the transaction: the
export does not claim separate success for each member. `job.progress()`
summarizes confirmed, unconfirmed and publication outcomes.
`job.execution(ordinal)` reads the original native receipt under the current
reader's permissions.

## Use the CLI

The installation is its feature ID; select it explicitly when several Lists
installations exist. Create takes the complete preparation input as lossless
JSON. Deployment, business authentication and native request controls use the
same transport as [`bijection component`](/cli/reference/component).

```bash theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
bijection component lists prepareRun --schema --format json
bijection bulk create --installation lists --args-file preparation.json --request-id prepare-1 --expected-contract PREPARE_CONTRACT
bijection bulk inspect JOB_ID
bijection bulk review JOB_ID --after null --limit 25
bijection bulk submit JOB_ID --plan-digest REVIEWED_DIGEST --request-id submit-1 --expected-contract SUBMIT_CONTRACT
bijection bulk approve JOB_ID --plan-digest REVIEWED_DIGEST --after null --limit 25 --request-id approve-page-1 --expected-contract APPROVE_CONTRACT
bijection bulk follow JOB_ID --timeout 30s
bijection bulk results JOB_ID --after null --limit 25
bijection bulk progress JOB_ID
bijection bulk execution JOB_ID --ordinal 0
bijection bulk cancel JOB_ID --request-id cancel-1 --expected-contract CANCEL_CONTRACT
bijection bulk recover JOB_ID --request-id recover-1 --expected-contract RECOVER_CONTRACT
bijection bulk purge JOB_ID --request-id purge-1 --expected-contract PURGE_CONTRACT
```

Review and approve every page before treating review as complete. Discover each
control's contract with
`bijection component lists CALLABLE --schema --format json`. Every control
requires an explicit `--request-id` and `--expected-contract`; retain these and
its exact original input before dispatch. Each approval page has its own control
ID. After an uncertain reply, repeat that exact control invocation with
`--recover` to inspect its original native request. This differs from
`bulk recover`, which resumes interrupted scheduler attempts. Never create a
replacement job to resolve a lost reply. `follow` streams until its finite
observation deadline or interruption and never cancels a job.

## Completion, cancellation and recovery

```mermaid theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
stateDiagram-v2
  [*] --> preparing
  preparing --> prepared: Exact proposals retained
  prepared --> awaiting_review: Submit exact plan digest
  awaiting_review --> running: All required approvals ready
  running --> draining: All local decisions recorded
  draining --> completed: Accepted external work settled
  preparing --> stopped: Refusal, expiry or interrupted attempt
  running --> stopped: Refusal, changed review or interrupted attempt
  stopped --> preparing: Recover original preparation attempts
  stopped --> awaiting_review: Recover original execution attempts
  preparing --> cancelled: Cancel
  prepared --> cancelled: Cancel
  awaiting_review --> cancelled: Cancel
  running --> cancelled: Cancel remaining work
```

Cancellation revokes pending execution authority, including queued attempts of
an already stopped job. It does not roll back accepted operations or cancel
their durable external calls. Their original receipts remain readable. An
unknown external outcome remains unresolved until the native reconciliation
contract supplies evidence; the bulk coordinator does not resend it or convert
it to failure.

With `on_refusal: "stop"`, a refusal stops further dispatch. Requests already
queued within the parallelism window may still finish. Choose parallelism one
when each decision must precede dispatching the next request.

System execution faults retry the same native request with scheduler backoff, up
to eight system failures per scheduled child job. The original authority expiry
also bounds acceptance. A guest/business refusal does not enter this retry loop.
Exhaustion stops the coordinator and retains the original inputs and identity
for explicit recovery.

To re-evaluate changed inputs, supply `predecessor_run` to `bulk.create` (or in
the CLI preparation JSON). The new intention links to that job. Creation refuses
until the predecessor is terminal, its original scheduler attempts have stopped
and all accepted external outcomes have settled; it then revokes the old pending
authority. This is a new reviewed intention, not recovery or a duplicate-credit
exemption: the business operation still enforces invariants across jobs.

`recover` applies only to an interrupted native scheduler attempt while the
original authority remains valid. It preserves each child request identity,
arguments, proposal and retained code. Expired authority, a changed proposal or
a business refusal requires a newly reviewed intention. Execution authority ends
at the earlier of the requested expiry, the authenticated parent's expiry and 30
days. Native admission rechecks current function access and every selected
resource's permission when accepting each request.

`completed` means the local requests are decided and accepted external work has
settled. It does not mean all effects succeeded: inspect `is_successful`,
refusals and native receipts. A stopped or cancelled job can still have accepted
external work in progress.

## Bounds and retention

A complete plan contains 1–64 requests, each with 1–100 objects and at most 16
KiB of frozen arguments. An object-target operation uses one object per request.
Parallelism is 1–16; an installation admits at most 32 active jobs. A list
contains at most 5,000 members and retains at most 32 jobs. Preparation also
fits the native transaction's read and write budgets; these limits do not
guarantee that every complex 5,000-object plan fits.

The five newest runs appear in the dashboard; saved job IDs retain access to
older runs. Retained jobs are never silently evicted to admit another. A list
with retained job history cannot be deleted. Capacity exhaustion refuses a new
job explicitly. `job.purge()` or `bulk purge` explicitly erases coordination
history only after the job is terminal, every original scheduler attempt has
stopped and all accepted external work has settled. Native accepted request
identities remain occupied under their own retention owner.

Callback code custody is tied to the retained job records. Its execution
authority still expires independently; expiry does not erase evidence needed for
unresolved outcomes. Native scheduler garbage collection does not permanently
block erasure of settled job history.

The fixed list must already be ready. Filling a list from Explore remains a
separate caller-driven paging operation; SDK and CLI callers can perform those
explicit reads and `fillStep` writes without opening a browser.


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