0013. Limits and plans live in id; services enforce and report
Status: accepted, 2026-10-09
Context
pages had its own quota table (deploys and storage per team), and every service would grow one: apps
(CPU, memory, apps), env (sets), ai (tokens a month), status (monitors). Then a team's limits are spread
over many panels, nobody sees them together, and there is no way to sell or hand out a bundle ("this
customer gets the Business plan"). Projects inside a team also need room of their own: previews of one
project shouldn't eat the whole team's storage.
Decision
- Limits are declared by services: a key, a title, a unit (
count, bytes, millicores,
tokens), e.g. pages.deploys, pages.storage, pages.deploy_size, apps.memory. A service sends
its declarations to id with its usage reports, so id and its panel know every limit without a
hard-coded list.
- id stores the values, on four levels, each optional:
- Plans: instance-wide named bundles of limits (
Free, Team, Business, any). Superusers
manage them. One plan may be the default for personal teams and one for other teams.
- Team: a plan (or the default) plus overrides per limit (a customer gets more storage).
- Project: caps inside the team's limits (previews of one project get at most 2 GB).
- Environment: later, the same as projects.
A missing value means no limit at that level. The effective limit of a project is the smallest of its
cap and its team's limit; usage counts against both.
- Services enforce: they read the effective limits with their client credentials
(
GET /api/v1/limits?service=), cache them for a minute, check team and project before creating
anything, and say which level stopped them ("the team's plan Free allows 100 deploys").
- Services report usage (
POST /api/v1/usage, every few minutes and after changes): used amounts per
team and project for their keys. id shows limits and usage together on the team's and the project's
Limits tab, and later warns near limits (notify).
- Capacity bound to one place stays with its service: a node's allocatable memory and which teams may
use it belong to apps (ADR 0005 grants); plans cap the totals across nodes.
Consequences
- One screen per team and per project shows every service's limits and usage; plans make
"enterprise" a configuration, not code.
- A service without id's answer keeps the last limits it had; on a fresh start it allows creation and
logs it, so an id outage doesn't stop deploys.
- pages drops its quota table and its quota settings; its Quotas page becomes a usage view linking to id.