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

# check_charges

> SPEC-v0.9 §3.3.1's predicate, in one place so three backends cannot drift on it.

`ctrlrun.state.check_charges` — function, defined at `src/ctrlrun/state.py:569`

```python theme={null}
from ctrlrun.state import check_charges
```

```python theme={null}
def check_charges(charges: tuple[Charge, ...], spent: Callable[[Charge], int]) -> None
```

SPEC-v0.9 §3.3.1's predicate, in one place so three backends cannot drift on it.

`spent` is the store's own sum of un-released rows for this charge's `(grant_id, metric)`
over `[now - window, now]`. The comparison is **inclusive**, matching §2.2's "what the sum may
reach": a `limit: 0` grant therefore permits an action only if its metric value is 0, and
§3.3.1 records that a zero limit does not stop a grant, since it permits unboundedly many
zero-valued actions.

Pure, like `plan_reservation` and for the same reason: every store decides here rather than
each deciding for itself, so the arithmetic is one function a test can reach directly.

**Every charge is evaluated, including several on one `(grant_id, metric)`.** That is §2.2's
own motivating shape: a grant with two budgets on `amount`, 100,000 a day and 500,000 a month,
is "the first thing an operator asks for", and it arrives here as two charges differing only
in `limit` and `window`. Both predicates run; §3.4's key then writes **one** row, which is
right, because it is one spend measured against two windows.

**What is refused is two charges on one `(grant_id, metric)` carrying different amounts.**
A charge is invisible to its sibling here (each is compared against the *stored* sum), and
§3.4's key carries no window, so differing amounts would collapse to whichever row landed
first and the ledger would under-record the spend. Nothing legitimate produces that: the
amount comes from the action's own metric value, so two budgets on one metric always agree,
and §2.7's per-ancestor charges are distinct grants.

An earlier version refused **any** duplicate pair, which made §2.2's shape die at execute
with no receipt: the loader accepted the document, observe mode reported it clean, and
`ctrlrun verify` could not grade it. An independent review found it.

## Next

* [Python API index](/docs/reference/api/index).
* [Get started](/docs/get-started/quickstart) · [Why](/docs/why).


## Related topics

- [Claims](/docs/CLAIMS.md)
- [Charge](/docs/reference/api/state-Charge.md)
- [Threat model](/docs/THREAT_MODEL.md)
- [Not only agents](/docs/not-only-agents.md)
- [Agent Execution Risk Check](/risk-check.md)
