Jadpo is experimental and not ready for production. These docs follow the compiler's main branch as of 5 Nov 2026. What's built · Install 0.3.0
jadpo
ReferenceBatches

Background workBatches

Batches, tenants and grants

On this page

In an app shared by several customers, each one a tenant (Tenancy), a batch reaches only one tenant's records: the one its saved values name. Without a tenant:, a batch reaches every record the system is granted.

Which tenant a run reaches

Each page's read, its items and finished: act as the system, limited to the tenant that the run's saved values of the tenant's id type name (Background work and tenants). These saved values count:

  • a from: argument, a value each: or finished: captures, or key:, whose own type is the tenant's id, such as Household.id, or that type with none allowed;
  • a field declared with that type, as a reference to the tenant is, such as rent_round.society_id.

A tenant's id held inside a saved record doesn't count. The run reads the values from what it saved, so it has the same tenant each time it resumes.

Values that all name the same tenant limit the run to that tenant, as with key: household_id beside an each: capturing household_id. A run that saves no such value, one whose value is none, or one with values naming different tenants, reaches no tenant's records, only those every tenant shares.

What a batch needs from policy

A batch runs as the system, so what it reaches must let Access.system in, as for a schedule or a subscriber (Policy and background work). Suppose what from:'s query reads, or what the action each: or finished: calls reaches (itself or through what it calls), has a grant list, a field's own list or a line under actions: that doesn't name Access.system, or is a listed query. Then that is policy.system_grant_missing, at that query's or action's call. Each call is checked, so one batch may be refused at more than one.

A batch's query that only Access.public may read:

example
type ShelfCode = Text {    min_length: 1    max_length: 40}type ItemPage = Input {    page_size: PageSize {        default: 25    }    after: PageCursor {        optional: true    }}entity Item {    id: Uuid    code: ShelfCode    seen: Bool    policy: {        create: [Access.authenticated]        read: [Access.public]        update: [Access.system]    }}query items(page: ItemPage) -> Page<Item> {    from: Item    order: [Item.id asc]    page: {        size: page.page_size        after: page.after    }}action mark_seen(item: Item) -> Unit {    var seen: Item = Item.update(item) {        set: {            seen: true        }    }    assert seen.seen}action sweep_shelf(page: ItemPage) -> BatchRun {    return batches.each(from: items(page), each: item -> mark_seen(item))}
jadpo checkcheck failed: 1 error, 1 file

error items runs as the system, which only Access.public lets read Item

--> main.jadpo:46:31

   |46 |     return batches.each(from: items(page), each: item -> mark_seen(item))   |                               ^^^^^^^^^^^ runs as `Access.system`
reason
A batch runs as the system, which an entity grants background work by naming Access.system; Item's read: names Access.public and not Access.system, and anyone at all is never how background work is let in.
next
Add Access.system to Item's read:, or reach Item from a route instead (human)
rule
policy.system_grant_missing
help
https://jadpo.dev/docs/errors/policy.system_grant_missing

A batch's action changing what only Access.public may change:

example
type ShelfCode = Text {    min_length: 1    max_length: 40}type ItemPage = Input {    page_size: PageSize {        default: 25    }    after: PageCursor {        optional: true    }}entity Item {    id: Uuid    code: ShelfCode    seen: Bool    policy: {        create: [Access.authenticated]        read: [Access.public, Access.system]        update: [Access.public]    }}query items(page: ItemPage) -> Page<Item> {    from: Item    order: [Item.id asc]    page: {        size: page.page_size        after: page.after    }}action mark_seen(item: Item) -> Unit {    var seen: Item = Item.update(item) {        set: {            seen: true        }    }    assert seen.seen}action sweep_shelf(page: ItemPage) -> BatchRun {    return batches.each(from: items(page), each: item -> mark_seen(item))}
jadpo checkcheck failed: 1 error, 1 file

error mark_seen runs as the system, which only Access.public lets update Item

--> main.jadpo:46:58

   |46 |     return batches.each(from: items(page), each: item -> mark_seen(item))   |                                                          ^^^^^^^^^^^^^^^ runs as `Access.system`
reason
A batch runs as the system, which an entity grants background work by naming Access.system; Item's update: names Access.public and not Access.system, and anyone at all is never how background work is let in.
next
Add Access.system to Item's update:, or reach Item from a route instead (human)
rule
policy.system_grant_missing
help
https://jadpo.dev/docs/errors/policy.system_grant_missing

Markdown for agents: /docs/batches-tenants-and-grants.md.Built from Jadpo main at aee2e8f.