The cloud plugin should enforce a per-plan AI token allowance by implementing the usage half of the Entitlements seam. Core already gives the plugin what it needs: it gates each turn with remaining($owner, 'ai_tokens') and meters with consume($owner, 'ai_tokens', $n), both keyed on the polymorphic owner. The plugin supplies the counter storage, resolves the owner's allowance, and binds its implementation over the default AllowAllEntitlements.
Two things aren't in place yet: the owner's active plan is never persisted (plans exist only as marketing config), and core meters the agent route only, so a user can spend tokens on other routes without touching their allowance.
Checklist
Acceptance
- A user over their allowance gets a quota-exceeded response from
POST /api/v1/ai/chat.
- Usage rolls over monthly.
- A deployment with the cloud plugin disabled stays unlimited.
- A core test confirms the counter matches the tokens core recorded for a turn.
The cloud plugin should enforce a per-plan AI token allowance by implementing the usage half of the
Entitlementsseam. Core already gives the plugin what it needs: it gates each turn withremaining($owner, 'ai_tokens')and meters withconsume($owner, 'ai_tokens', $n), both keyed on the polymorphic owner. The plugin supplies the counter storage, resolves the owner's allowance, and binds its implementation over the defaultAllowAllEntitlements.Two things aren't in place yet: the owner's active plan is never persisted (plans exist only as marketing config), and core meters the agent route only, so a user can spend tokens on other routes without touching their allowance.
Checklist
remaining()can resolve an allowance (link an existing plan/subscription issue if one already covers this).token_allowanceand a monthly reset period to each plan.ai_usage_countersin the plugin, keyed polymorphically:morphs('owner'),period_start,tokens_used,unique(owner_type, owner_id, period_start).remaining('ai_tokens')as allowance minus current-period usage andconsume('ai_tokens', n)as an increment. Bind over the default.consume()nor record usage; only the agent route does.consume()at most once per assistant message, keyed on the message id core already owns, so a job retry doesn't double-count.Acceptance
POST /api/v1/ai/chat.