Team Management
Lets a Team / Enterprise owner share their package allowance with members under per-member caps. Built on the per-product entitlement model (migrations 195β198); this is issue #1347.
Model
- The teamβs allowance is the OWNERβs package. The owner holds
team/enterpriseinaccount_packages(from the live self-serve checkout). The shared pool counter is the ownerβsentitlement_usagerow for the quota group β so the team allowance always tracks the ownerβs live subscription (a cancel/downgrade flows through automatically). There is no duplicate package record on the team. team_member_quotasβ per-member, per-group cap. A missing row orNULLlimit = unlimited within the pool. Caps may oversubscribe the pool (sum of caps is not constrained); they are independent per-member lids.team_member_usageβ per-member, per-group consumption this period (the member-cap source + the per-member figure the dashboard renders). Resets on the same boundary as the pool.billing_packages.team_poolβ TRUE forteam/enterprise. Only a team-capable owner package engages the team path.
Request-time resolution (migration 200)
consume_entitlement / entitlement_status / refund_entitlement consult
resolve_team_consumption(user, group). A member consumes from the team pool
when all hold:
profiles.credit_source = 'team:<uuid>'(the member is enrolled), AND- they are the team owner or an active member (
joined_atset), AND - the owner holds an active,
team_poolpackage whose entitlement for the group is quota or unlimited.
Otherwise the existing personal path runs unchanged.
In the team path (quota groups only β remediation), both ceilings are hard
caps: the pool (ownerβs entitlement_usage vs the package limit) and the
member cap (team_member_usage vs team_member_quotas). Either blocks.
Unlimited groups (paid-plan audits) are pass-through, exactly like personal.
API (/api/teams, both entry points)
GET /:teamId/entitlementβ dashboard: pool + per-member caps & usage.PUT /:teamId/members/:userId/quota{ quotaGroup, limit }β set a cap (limit: null= unlimited within pool). Metered groups only; capping an unlimited group is rejected. Admin+.DELETE /:teamId/members/:userId/quota?quotaGroup=β¦β clear a cap. Admin+.PUT /:teamId/members/:userId/pool{ enrolled }β enroll/unenroll a member by pointing theircredit_sourceat the team (or back topersonal). A member only draws on the pool while enrolled β this is the activation switch. Admin+.
UI: the Allowance tab in the team dashboard
(apps/web/src/app/teams/[teamId]/TeamAllowancePanel.tsx).
Legacy
The cents-based team pool (teams.credit_balance, team_members.credit_quota / credits_used, deduct_team_credits, team_credit_transactions) is left in
place (additive migrations) but is no longer the entitlement gate. Removing it
is a separate cleanup.
Tests
supabase/tests/team_entitlement.test.sqlβ pool ceiling, member hard cap, oversubscribe, owner-consumes, idempotency, exhaustion, refund (both counters), period reset, and the four personal-path fallbacks.workers/api/src/__tests__/routes/teams.test.tsβ the four endpoints.