Jadpo is experimental and not ready for production. These docs follow the compiler's main branch as of 30 Oct 2026. What's built · Install 0.1.0
jadpo
Reference: Jobs and events

Jobs and events

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 (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 names Access.public and not Access.system. 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))}
text
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_missingcheck failed: 1 error, 1 file

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))}
text
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_missingcheck failed: 1 error, 1 file

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