What a tenant (or customer) may do right now: its plan’s features plus per-subject overrides (a negotiated “500 seats on Pro”, a temporary grant), cached per subject, and enforceable from a route with the right HTTP answer. EntitlementError carries the runtime’s structural error marker, so @venturekit/runtime’s boundary renders it as { error: { code, message, details } } with the proper status without this package depending on the runtime.
EntitlementsResolver — forSubject(subjectId) resolves the subject’s plan (getPlanId is your lookup: subscription row, tenant column; null = no plan) and overrides, cached per subject (ttlMs default 60 000, 0 disables); invalidate(subjectId?) after a plan change
The pure resolution behind it → Entitlements: plan features first, then overrides on top (source: 'plan' | 'override')
EntitlementOverrides
Record<featureKey, { enabled?, limit? }> — limit: null means unlimited
EntitlementError
codePLAN_UPGRADE_REQUIRED (402, feature not in plan), PLAN_LIMIT_REACHED (403, details.limit / details.usage for the upgrade prompt) or PLAN_UNKNOWN (403, no active plan); details.planId / details.featureKey always
Entitlements methods:
Method
Description
has(featureKey)
Enabled in plan or override
limitOf(featureKey)
number, null (unlimited) or undefined (feature not available)
require(featureKey)
Throws EntitlementError when not available
checkLimit(featureKey, usage)
LimitCheck{ allowed, limit, usage, remaining } — allowed is also true for unlimited
requireWithinLimit(featureKey, getUsage)
Fetches usage only when the limit is finite, then enforces (403 at the cap, 402 when not in plan); returns the LimitCheck for headers / UI hints
toQuotas(keyMap?)
The finite limits as a plain map — the shape @venturekit-pro/tenancy’s TenantContext.quotas / createQuotaMiddleware reads; keyMap renames ({ seats: 'maxUsers' })