# Batches, tenants and grants

In an app shared by several customers, each one a **tenant**
([Tenancy](tenancy.md)), 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](background-work-and-tenants.md)). 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](background-work.md)). 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:

```jadpo
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_missing

check failed: 1 error, 1 file
```

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

```jadpo
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_missing

check failed: 1 error, 1 file
```
