44 KiB
Entitlements -- Model Reference
Module: entitlements
Projection of: data-model.md entitlements section
Entitlements answers: "What capabilities are available, who provisioned them, and how are they distributed?"
The resource pool is the bridge between the financial world (billing accounts, subscriptions, purchases) and the resource world (workspaces, boolean entitlements, numeric entitlements, credits). It decouples "who pays" from "who uses" while maintaining a clear audit trail between them.
resource_pools
A resource pool aggregates boolean entitlements, numeric entitlements (limits and quotas), and credits from one or more provision sources. Workspaces draw capabilities from pools via pool assignments. Pools are the unit of resource sharing: multiple workspaces can share a pool (shared mode), or a pool can be dedicated to a single workspace.
Pools are auto-created for simple cases and explicitly managed for complex ones:
- When a billing account is created, a default pool is auto-created.
- When a workspace is created, it is auto-assigned to its organization's default billing account's default pool.
- Administrators can create additional pools for departmental isolation, client-funded projects, or cross-organization sponsorship.
| Field | Type | Purpose |
|---|---|---|
pool_id |
UUID | Primary key |
org_id |
UUID | FK -> organization.organizations. The organization this pool belongs to (governance scope). |
name |
VARCHAR(255) | Display name. Auto-generated for default pools. |
slug |
VARCHAR(100) | URL-safe identifier. Unique within the organization. |
pool_type |
VARCHAR(20) | default (auto-created with billing account), shared (explicit multi-workspace), dedicated (explicit single-workspace). |
is_auto_managed |
BOOLEAN | If true, system manages this pool automatically. Default pools are auto-managed. |
description |
TEXT | What this pool is for. |
status |
VARCHAR(20) | active, suspended, archived. |
suspended_at |
TIMESTAMPTZ | When most recently suspended. |
archived_at |
TIMESTAMPTZ | When archived. |
created_at |
TIMESTAMPTZ | |
updated_at |
TIMESTAMPTZ |
Constraints:
slugis unique within an organization.
Status lifecycle governed by the Soft-Delete and Terminal State Policy.
Relationships:
resource_pools(0..*) -> (1)organizations: Pools belong to an organization (governance scope).FK -> organization.organizationsresource_pools(1) -> (0..*)pool_provisions: Pools receive provisions from billing sources.resource_pools(1) -> (0..*)pool_assignments: Pools are assigned to workspaces.resource_pools(1) -> (0..*)boolean_entitlements: Pools hold binary capabilities.resource_pools(1) -> (0..*)numeric_entitlements: Pools hold numeric limits and quotas.resource_pools(1) -> (0..*)pool_ondemand_config: Pools may have on-demand usage configuration.
pool_provisions
A pool_provision records that a resource pool has been provisioned with capabilities from a specific source. The source may be a subscription (recurring agreement), a purchase (one-time transaction), or a grant (administrative/promotional access).
Pool provisions are the uniform interface through which all access mechanisms connect to resource pools. The entitlement system queries provisions to determine what capabilities a pool should have. It does not need to know whether the provision came from a subscription, purchase, or grant.
| Field | Type | Purpose |
|---|---|---|
provision_id |
UUID | Primary key |
pool_id |
UUID | FK -> resource_pools. The pool being provisioned. |
billing_account_id |
UUID | FK -> billing.billing_accounts. The financial source. NULL for grants that target an org or person directly. |
subscription_id |
UUID | FK -> billing.subscriptions. If this provision comes from a subscription. NULL otherwise. |
purchase_id |
UUID | FK -> billing.purchases. If this provision comes from a purchase. NULL otherwise. |
grant_id |
UUID | FK -> grants. If this provision comes from a grant. NULL otherwise. (Intra-module, Decision 106.) |
entitlement_set_id |
UUID | FK -> entitlement_sets. Which entitlement set's rules apply. Resolved at provision-creation time: from products.entitlement_set_id for subscription/purchase sources, or directly from the grant for grant sources. Always populated. |
quantity |
INTEGER | For per-unit sources: how many units. Determines resource value multipliers via entitlement_set_rules. |
status |
VARCHAR(20) | active, suspended, ended. Derived from source status. |
activated_at |
TIMESTAMPTZ | When this provision became active. |
suspended_at |
TIMESTAMPTZ | When suspended, if applicable. |
ended_at |
TIMESTAMPTZ | When ended, if applicable. |
created_at |
TIMESTAMPTZ | |
updated_at |
TIMESTAMPTZ |
Constraints (exclusive arc -- source):
CHECK (
(subscription_id IS NOT NULL AND purchase_id IS NULL AND grant_id IS NULL)
OR (subscription_id IS NULL AND purchase_id IS NOT NULL AND grant_id IS NULL)
OR (subscription_id IS NULL AND purchase_id IS NULL AND grant_id IS NOT NULL)
)
Exactly one of subscription_id, purchase_id, or grant_id must be set.
Note: billing_account_id is not constrained to the same org_id as the pool. This enables cross-organization provisioning (Decision 53).
How each source type creates provisions:
| Source | Trigger | Pool Provision Status |
|---|---|---|
| Subscription (active/trialing) | Subscription item activated | active |
| Subscription (past_due) | Payment failed, grace period | active (configurable) |
| Subscription (unpaid/paused) | Retries exhausted or paused | suspended |
| Subscription (canceled) | Agreement terminated | ended |
| Purchase (completed) | Transaction completed | active (indefinitely) |
| Purchase (refunded) | Full refund issued | ended |
| Grant (active) | Grant valid_from reached | active |
| Grant (expired) | Grant valid_until reached | ended |
| Grant (revoked) | Admin revocation | ended |
Multi-item subscriptions: A subscription with three items creates three pool_provision records -- one per item. Each references a different entitlement set (resolved from each item's product) and activates that set's rules on the pool.
Relationships:
pool_provisions(0..*) -> (1)resource_pools: Provisions target a pool.pool_provisions(0..*) -> (0..1)billing_accounts: Provisions may have a financial source.FK -> billing.billing_accounts- Exclusive arc -- source (
subscription_id/purchase_id/grant_id):pool_provisions(0..*) -> (0..1)subscriptions: Provisions may come from a subscription.FK -> billing.subscriptionspool_provisions(0..*) -> (0..1)purchases: Provisions may come from a purchase.FK -> billing.purchasespool_provisions(0..*) -> (0..1)grants: Provisions may come from a grant (intra-module).
pool_provisions(0..*) -> (1)entitlement_sets: Provisions always resolve to an entitlement set for materialization. (intra-module)
pool_provision_ladders
A pool_provision_ladder row records that a given pool provision occupies a specific rung on a plan ladder. The junction separates two orthogonal facts that Amendment #1 to Doc 31 identified as improperly conflated in the original "Option A" design: the commercial fact ("this billing source activated this pool at time T") belongs to pool_provisions, while the catalog-shape fact ("this activation occupies this ladder rung") belongs here. That separation honors doc-32's four-orthogonal-concerns framing and enables a pool provision arising from a multi-ladder bundle to register on several ladders with a single pool_provisions row rather than the redundant proliferation of multiple provision rows per activation.
The exclusion constraint is the table's raison d'être. Because PostgreSQL's GiST exclusion operator cannot express cross-table predicates, the columns pool_id, status, activated_at, and ended_at are denormalized from the parent provision and kept in sync by an AFTER UPDATE trigger on pool_provisions (see below). The constraint then enforces "at most one active provision per (pool, ladder)" across overlapping temporal windows: two rows with non-overlapping [activated_at, ended_at) windows on the same (pool_id, plan_ladder_id) pair are permitted, enabling Lago-style dual-row scheduled transitions. The WHERE (status = 'active') partial predicate ensures the GiST index is empty for ended provisions and entirely absent for cooperatives with no ladder enrollment whatsoever, honoring the self-sufficiency constraint of Decision 87.
| Field | Type | Purpose |
|---|---|---|
provision_id |
UUID | PK component. FK -> pool_provisions. The provision that occupies this ladder rung. |
plan_ladder_id |
UUID | PK component. FK -> billing.plan_ladders. The ladder on which the provision is registered. |
pool_id |
UUID | Denormalized from pool_provisions.pool_id; required for the GiST exclusion predicate. Kept in sync by trigger. |
status |
VARCHAR(20) | Denormalized from pool_provisions.status. Binary: 'active' or 'ended'. Trial state is carried on the source subscription, not here (see note below). Kept in sync by trigger. |
activated_at |
TIMESTAMPTZ | Denormalized from pool_provisions.activated_at. Lower bound of the exclusion window. Kept in sync by trigger. |
ended_at |
TIMESTAMPTZ | Denormalized from pool_provisions.ended_at. Upper bound of the exclusion window; NULL means unbounded. Kept in sync by trigger. |
Primary key: (provision_id, plan_ladder_id)
DDL:
CREATE TABLE entitlements.pool_provision_ladders (
provision_id UUID NOT NULL REFERENCES entitlements.pool_provisions(provision_id) ON DELETE CASCADE,
plan_ladder_id UUID NOT NULL REFERENCES billing.plan_ladders(plan_ladder_id),
-- Denormalized from pool_provisions; kept in sync by trigger:
pool_id UUID NOT NULL,
status VARCHAR(20) NOT NULL,
activated_at TIMESTAMPTZ NOT NULL,
ended_at TIMESTAMPTZ,
PRIMARY KEY (provision_id, plan_ladder_id),
CONSTRAINT one_active_provision_per_pool_per_ladder
EXCLUDE USING gist (
pool_id WITH =,
plan_ladder_id WITH =,
tstzrange(activated_at, ended_at, '[)') WITH &&
)
WHERE (status = 'active')
);
CREATE INDEX pool_provision_ladders_pool_idx ON entitlements.pool_provision_ladders (pool_id);
The pool_provision_ladders_pool_idx index supports the canonical "what plan is this pool on?" query, which filters by pool alone without specifying a ladder — the primary-key index covering (provision_id, plan_ladder_id) does not serve this access pattern.
Note on trial state (status field). The junction's status vocabulary is intentionally binary: 'active' or 'ended'. Trial state is carried on the source subscription (billing.subscriptions.status = 'trialing'), not on this junction. Adding 'trialing' here would either require constraint participation — making trial and active mutually exclusive and breaking seamless conversion — or would be non-participating, in which case it redundantly projects information already present on the subscription. The vehicle-vs-position distinction (modules/GLOSSARY.md §5.1) makes the reasoning precise: trial is a vehicle state (how the subscription is currently running), not a position state (which catalog slot the pool currently holds). To answer "is this pool trialing tier T on ladder L?", join the junction against pool_provisions and billing.subscriptions:
SELECT s.status, s.trial_start, s.trial_end
FROM entitlements.pool_provision_ladders pl
JOIN entitlements.pool_provisions p ON p.provision_id = pl.provision_id
JOIN billing.subscriptions s ON s.subscription_id = p.subscription_id
WHERE pl.pool_id = :pool_id
AND pl.plan_ladder_id = :ladder_id
AND pl.status = 'active'
AND s.status = 'trialing';
Relationships:
pool_provision_ladders(0..*) -> (1)pool_provisions: Each junction row belongs to exactly one provision. Cascade-deleted when the provision is deleted.pool_provision_ladders(0..*) -> (1)plan_ladders: Each junction row references a ladder rung.FK -> billing.plan_ladders
pool_provision_transitions
A pool_provision_transition is the audit record of a ladder-position change affecting a pool provision — an initiation, upgrade, downgrade, or end. The table answers "what tier did this pool hold at time T, who put it there, and why?" — a question that cannot be reconstructed from pool_provisions and pool_provision_ladders timestamps alone once actor attribution and reason-for-change are required. It complements, rather than replaces, billing.subscription_changes: that table remains canonical for commercial mutations of a subscription (status transitions, period boundaries, amount changes); this table is canonical for plan-position history of a pool. Subscription-driven ladder attachments are recorded in both — intentionally — because they answer different questions (see GLOSSARY §3, "pool provision transition").
The table is scoped narrowly to ladder-position changes. Suspension and resumption lifecycle are not recorded here — those are carried by pool_provisions.status and, for subscription-driven suspensions, subscription_changes. This scope discipline is deliberate: it keeps transition_type semantically clean and makes the table's eventual absorption into a generic audit.log module mechanical rather than requiring semantic rewriting (see "View-shape contract" below).
| Field | Type | Purpose |
|---|---|---|
transition_id |
UUID | Primary key |
pool_id |
UUID | FK -> resource_pools. The pool whose ladder position changed. Denormalized for query convenience; the same value is reachable via provision_id → pool_provisions.pool_id. |
provision_id |
UUID | FK -> pool_provisions. The provision whose ladder attachment this transition records. |
plan_ladder_id |
UUID | FK -> billing.plan_ladders. The ladder on which the transition occurred. NULL for off-ladder transitions (e.g., an add-on grant's lifecycle). |
from_rank |
INTEGER | The rank the pool held immediately prior to the transition. NULL on initiate (no prior attachment). |
to_rank |
INTEGER | The rank the pool holds immediately after the transition. NULL on end (no subsequent attachment). |
transition_type |
VARCHAR(50) | initiate, upgrade, downgrade, or end. Strictly scoped to ladder-position changes. |
actor_type |
VARCHAR(50) | operator, system, or webhook. Identifies the agent responsible for the transition. |
actor_id |
UUID | FK -> identity.persons when actor_type = 'operator'. NULL-permitted for system and webhook actors today, with room to populate it if webhook authentication later admits actor attribution. |
reason |
TEXT | Human-readable explanation. For operator actions, typically a reason the operator entered; for system transitions, a code indicating the trigger (e.g., "subscription_activated", "reapply_defaults"). |
effective_at |
TIMESTAMPTZ | When the transition took effect in the business sense. |
created_at |
TIMESTAMPTZ | When the row was recorded. Distinct from effective_at to accommodate scheduled and backdated transitions. |
DDL:
CREATE TABLE entitlements.pool_provision_transitions (
transition_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
pool_id UUID NOT NULL REFERENCES entitlements.resource_pools(pool_id),
provision_id UUID NOT NULL REFERENCES entitlements.pool_provisions(provision_id),
plan_ladder_id UUID REFERENCES billing.plan_ladders(plan_ladder_id),
from_rank INTEGER,
to_rank INTEGER,
transition_type VARCHAR(50) NOT NULL
CHECK (transition_type IN ('initiate', 'upgrade', 'downgrade', 'end')),
actor_type VARCHAR(50) NOT NULL
CHECK (actor_type IN ('operator', 'system', 'webhook')),
actor_id UUID REFERENCES identity.persons(person_id),
reason TEXT,
effective_at TIMESTAMPTZ NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
CONSTRAINT operator_actor_requires_id
CHECK (actor_type != 'operator' OR actor_id IS NOT NULL)
);
CREATE INDEX pool_provision_transitions_pool_idx
ON entitlements.pool_provision_transitions (pool_id, effective_at DESC);
CREATE INDEX pool_provision_transitions_provision_idx
ON entitlements.pool_provision_transitions (provision_id);
Rank conventions.
initiate:from_rank IS NULL,to_rankis the rank of the initial attachment.upgrade: both ranks populated,to_rankhigher thanfrom_rankby the ladder's ordering.downgrade: both ranks populated,to_ranklower thanfrom_rank, orto_rank IS NULLwhen the downgrade leaves the ladder empty and no re-application follows.end:to_rank IS NULL. Recorded when a provision's ladder attachment ends without a replacement. When a supersession transition ends the outgoing provision alongside activating a superseding one, both events are recorded as separate rows (oneend, oneinitiate/upgrade) rather than conflated.
View-shape contract. The columns are chosen to map directly onto a generic audit.log projection shaped as {resource_type, resource_id, actor_type, actor_id, action, occurred_at, recorded_at, payload}. The correspondence is: resource_type='pool_provision', resource_id=provision_id, action=transition_type, occurred_at=effective_at, recorded_at=created_at, payload={pool_id, plan_ladder_id, from_rank, to_rank, reason}. When the generic audit module graduates from the backlog, absorption of this table is mechanical rather than semantic: a UNION ALL view across pool_provision_transitions (and any peer specialized logs) yields the generic log without requiring rewrites. Should that absorption occur, this table may be deprecated into a view over audit.log without disturbing callers.
Relationships:
pool_provision_transitions(0..*) -> (1)resource_pools: Every transition is scoped to a pool.pool_provision_transitions(0..*) -> (1)pool_provisions: Every transition is attributed to a specific provision.pool_provision_transitions(0..*) -> (0..1)plan_ladders: Transitions are typically ladder-scoped; NULL permitted for off-ladder lifecycle.FK -> billing.plan_ladderspool_provision_transitions(0..*) -> (0..1)persons: Operator transitions reference the responsible person.FK -> identity.persons
Sync trigger: pool_provisions → pool_provision_ladders
Because the exclusion constraint on pool_provision_ladders operates against denormalized columns, those columns must remain identical to their counterparts on pool_provisions. An AFTER UPDATE trigger on pool_provisions provides this guarantee:
CREATE OR REPLACE FUNCTION entitlements.sync_pool_provision_ladders()
RETURNS TRIGGER LANGUAGE plpgsql AS $$
BEGIN
UPDATE entitlements.pool_provision_ladders
SET pool_id = NEW.pool_id,
status = NEW.status,
activated_at = NEW.activated_at,
ended_at = NEW.ended_at
WHERE provision_id = NEW.provision_id;
RETURN NEW;
END;
$$;
CREATE TRIGGER pool_provisions_sync_ladders
AFTER UPDATE OF pool_id, status, activated_at, ended_at
ON entitlements.pool_provisions
FOR EACH ROW EXECUTE FUNCTION entitlements.sync_pool_provision_ladders();
Contract. The trigger fires after any UPDATE that touches pool_id, status, activated_at, or ended_at on pool_provisions. It propagates the new values to all matching rows in pool_provision_ladders (there may be more than one if the provision spans multiple ladders in a bundle). It does not fire on INSERT — the application code that creates a provision is also responsible for creating the corresponding junction row(s). It fires on DELETE implicitly: pool_provision_ladders carries ON DELETE CASCADE, so no trigger is required for deletions.
The trigger is required for correctness of the exclusion constraint: if a provision's status transitions to 'ended' or its temporal window shifts (e.g., in a scheduled replacement), the junction rows must reflect that change immediately, before any subsequent insertion that would otherwise violate the constraint.
grants
A grant represents administrative, promotional, or exceptional access that is not backed by a commercial transaction. No money changes hands. Access is conferred by an authorized person. Grants are the non-financial provisioning source in the resource pool system -- they answer "what does this entity have?" rather than "who pays?" (Decision 106).
| Field | Type | Purpose |
|---|---|---|
grant_id |
UUID | Primary key |
entitlement_set_id |
UUID | FK -> entitlement_sets. The entitlement set this grant confers directly. NULL if product_id is set (resolved from the product's set). |
product_id |
UUID | FK -> billing.products. Optional. When a grant confers a specific commercial product (e.g., "granted the Pro plan by board decision"), the product is recorded for audit clarity. The entitlement set is resolved from products.entitlement_set_id at provision-creation time. NULL if entitlement_set_id is set directly. |
granted_to_billing_account_id |
UUID | FK -> billing.billing_accounts. If the recipient is a billing account. NULL otherwise. |
granted_to_org_id |
UUID | FK -> organization.organizations. If the recipient is an organization. NULL otherwise. |
granted_to_person_id |
UUID | FK -> identity.persons. If the recipient is a person. NULL otherwise. |
granted_by_person_id |
UUID | FK -> identity.persons. Who authorized this grant. |
grant_reason |
VARCHAR(50) | Classification: promotional, complimentary, legacy, sponsored, trial_extension, board_decision, other. |
description |
TEXT | Human-readable explanation. |
valid_from |
TIMESTAMPTZ | When the grant becomes effective. |
valid_until |
TIMESTAMPTZ | When the grant expires. NULL for indefinite. |
status |
VARCHAR(20) | active, expired, revoked. |
revoked_at |
TIMESTAMPTZ | When the grant was revoked, if applicable. |
revoked_by_person_id |
UUID | FK -> identity.persons. Who revoked it. |
revocation_reason |
TEXT | Why it was revoked. |
metadata |
JSONB | Additional grant attributes. |
created_at |
TIMESTAMPTZ | |
updated_at |
TIMESTAMPTZ |
Constraints (exclusive arc -- recipient):
CHECK (
(granted_to_billing_account_id IS NOT NULL AND granted_to_org_id IS NULL AND granted_to_person_id IS NULL)
OR (granted_to_billing_account_id IS NULL AND granted_to_org_id IS NOT NULL AND granted_to_person_id IS NULL)
OR (granted_to_billing_account_id IS NULL AND granted_to_org_id IS NULL AND granted_to_person_id IS NOT NULL)
)
Exactly one of granted_to_billing_account_id, granted_to_org_id, or granted_to_person_id must be set.
Constraints (entitlement source -- at least one):
CHECK (
entitlement_set_id IS NOT NULL OR product_id IS NOT NULL
)
At least one of entitlement_set_id or product_id must be set. When product_id is set, the entitlement set is resolved from products.entitlement_set_id at provision-creation time. When entitlement_set_id is set directly, the grant confers capabilities without a commercial product intermediary. Both may be set if a grant confers a product and the set is denormalized for query convenience, though the typical pattern is one or the other.
Relationships:
grants(0..*) -> (0..1)entitlement_sets: Grants may directly reference an entitlement set. (intra-module)grants(0..*) -> (0..1)products: FK -> billing.products. Grants may reference a product for audit clarity.- Exclusive arc -- recipient (
granted_to_billing_account_id/granted_to_org_id/granted_to_person_id):grants(0..*) -> (0..1)billing_accounts: FK -> billing.billing_accounts. Grants may target a billing account.grants(0..*) -> (0..1)organizations: FK -> organization.organizations. Grants may target an organization.grants(0..*) -> (0..1)persons(viagranted_to_person_id): FK -> identity.persons. Grants may target a person.
grants(0..*) -> (1)persons(viagranted_by_person_id): FK -> identity.persons. Grants are authorized by a person.grants(1) -> (0..*)pool_provisions: Grants create pool provisions (intra-module).
pool_assignments
A pool_assignment links a workspace to a resource pool. Workspaces draw their capabilities -- boolean entitlements, numeric entitlements, and on-demand access -- from their assigned pools.
Each workspace has exactly one primary pool assignment. Secondary assignments are optional and provide additional capabilities.
| Field | Type | Purpose |
|---|---|---|
assignment_id |
UUID | Primary key |
pool_id |
UUID | FK -> resource_pools. |
workspace_id |
UUID | FK -> organization.workspaces. |
is_primary |
BOOLEAN | Whether this is the workspace's primary pool. |
status |
VARCHAR(20) | active, suspended. |
suspended_at |
TIMESTAMPTZ | When most recently suspended. |
created_at |
TIMESTAMPTZ | |
updated_at |
TIMESTAMPTZ |
Constraints:
- Each workspace has exactly one primary pool assignment (
is_primary= true). - The combination of
pool_idandworkspace_idis unique.
Entitlement resolution across multiple pools:
- Boolean entitlements: Union semantics. If any assigned pool grants a capability, the workspace has it.
- Numeric entitlements (limits and quotas): Primary pool is consumed first. If exhausted, secondary pools are checked in assignment order.
- On-demand usage: Routed through the primary pool's on-demand configuration, with fallback to secondary pools.
Relationships:
pool_assignments(1..*) -> (1)workspaces: Every workspace has at least one pool assignment.FK -> organization.workspacespool_assignments(0..*) -> (1)resource_pools: Assignments reference a pool.
pool_ondemand_config
A pool_ondemand_config defines on-demand resource pricing available through a pool. Unlike pre-set quotas, on-demand resources are consumed without pre-commitment and billed after the fact. This is billing-module configuration, not an entitlement — it defines what happens at the boundary when entitlements are exhausted, or serves as the sole access gate for purely post-paid resources (Decision 104).
| Field | Type | Purpose |
|---|---|---|
config_id |
UUID | Primary key |
pool_id |
UUID | FK -> resource_pools. |
resource_key |
VARCHAR(100) | FK -> resource_keys. Resource identifier (e.g., api_calls, compute_minutes, bandwidth_bytes). Shared namespace with entitlement resource keys (Decision 103). |
provision_id |
UUID | FK -> pool_provisions. The provision that triggered this config's creation. NULL for manually configured entries. Provides lifecycle coupling (Decision 102). |
billing_account_id |
UUID | FK -> billing.billing_accounts. Who pays for on-demand consumption. |
rate_plan_id |
UUID | FK -> billing.prices. What rate applies to this on-demand resource. |
soft_limit |
BIGINT | Optional spending alert threshold (smallest currency unit). NULL = no alert. |
hard_limit |
BIGINT | Optional spending cap (smallest currency unit). NULL = unlimited. |
status |
VARCHAR(20) | active, suspended. |
suspended_at |
TIMESTAMPTZ | When most recently suspended. |
created_at |
TIMESTAMPTZ | |
updated_at |
TIMESTAMPTZ |
Relationships:
pool_ondemand_config(0..*) -> (1)resource_pools: Config belongs to a pool.pool_ondemand_config(0..*) -> (0..1)pool_provisions(viaprovision_id): Lifecycle coupling for provision-triggered config.pool_ondemand_config(0..*) -> (1)resource_keys(viaresource_key): Shared resource namespace.pool_ondemand_config(0..*) -> (1)billing_accounts: Config designates a payer.FK -> billing.billing_accountspool_ondemand_config(0..*) -> (1)prices: Config references a rate plan.FK -> billing.prices
boolean_entitlements
A boolean entitlement represents a binary capability that a resource pool has. Boolean entitlements are materialized from rule_type = 'boolean' entitlement_set_rules when pool provisions are activated. Toggle history (when is_enabled changes) is tracked exclusively through the audit log (Decision 43).
| Field | Type | Purpose |
|---|---|---|
entitlement_id |
UUID | Primary key |
pool_id |
UUID | FK -> resource_pools. The pool that has this entitlement. |
resource_key |
VARCHAR(100) | FK -> resource_keys. Machine-readable feature identifier (e.g., sites, custom_domains, api_access). |
is_enabled |
BOOLEAN | Whether this entitlement is currently active. |
source_provision_id |
UUID | FK -> pool_provisions. Which provision granted this entitlement. |
valid_from |
TIMESTAMPTZ | When this entitlement became active. |
valid_until |
TIMESTAMPTZ | When this entitlement expires. NULL means valid until provision ends. |
metadata |
JSONB | Additional entitlement attributes. JSONB Governance Policy applies. |
created_at |
TIMESTAMPTZ | |
updated_at |
TIMESTAMPTZ |
"Last funder standing" logic: When a provision ends, the system checks whether any other active provision on the same pool grants the same entitlement. If yes, the entitlement remains active. If no, it is deactivated.
Relationships:
boolean_entitlements(0..*) -> (1)resource_pools: Entitlements belong to a pool.boolean_entitlements(0..*) -> (1)pool_provisions: Entitlements are granted by a provision.boolean_entitlements(0..*) -> (1)resource_keys(viaresource_key): Shared resource namespace.
numeric_entitlements
A numeric entitlement represents a numeric limit or quota on resource usage within a resource pool. The numeric_entitlements table stores the definition (what the limit is and what type); mutable usage state is separated into numeric_entitlement_usage (Decision 49). Provenance -- which provisions contribute to the effective limit -- is tracked in numeric_entitlement_contributions (Decision 50).
The entitlement_type discriminator distinguishes two species within the numeric genus:
limit: Static allocation without a reset period (e.g., 5 workspaces). The application enforces a count; no usage tracking or reset.quota: Renewable consumption budget with a reset period (e.g., 1000 API calls/month). Usage is tracked and periodically reset.
| Field | Type | Purpose |
|---|---|---|
entitlement_id |
UUID | Primary key |
pool_id |
UUID | FK -> resource_pools. The pool this numeric entitlement belongs to. |
resource_key |
VARCHAR(100) | FK -> resource_keys. Machine-readable identifier (e.g., sites_limit, storage_bytes, api_calls). |
entitlement_type |
VARCHAR(20) | limit or quota. Discriminator for the numeric genus. |
resource_limit |
BIGINT | Effective limit. -1 means unlimited. Denormalized; recomputed from numeric_entitlement_contributions by the materialization pipeline. |
reset_period |
VARCHAR(20) | For quotas: daily, monthly, yearly. NULL for limits. |
created_at |
TIMESTAMPTZ | |
updated_at |
TIMESTAMPTZ |
Relationships:
numeric_entitlements(0..*) -> (1)resource_pools: Numeric entitlements belong to a pool.numeric_entitlements(1) -> (0..*)numeric_entitlement_contributions: Numeric entitlements receive contributions from provisions.numeric_entitlements(1) -> (1)numeric_entitlement_usage: Each numeric entitlement has a corresponding usage state record.numeric_entitlements(0..*) -> (1)resource_keys(viaresource_key): Shared resource namespace.
numeric_entitlement_contributions
A numeric entitlement contribution records a single provision's contribution to a numeric entitlement's effective limit. When a pool has multiple active provisions that grant the same resource, each provision creates a separate contribution. The entitlement's resource_limit is derived from its contributions using the applicable stacking policy (Decision 48).
| Field | Type | Purpose |
|---|---|---|
contribution_id |
UUID | Primary key |
entitlement_id |
UUID | FK -> numeric_entitlements. The numeric entitlement receiving this contribution. |
provision_id |
UUID | FK -> pool_provisions. The provision contributing. |
contributed_value |
BIGINT | The value this provision contributes. For resource_per_unit rules, this is resource_value x provision.quantity. |
stacking_policy |
VARCHAR(20) | The stacking policy that applies to this contribution. Denormalized from the rule at materialization time. |
created_at |
TIMESTAMPTZ | |
updated_at |
TIMESTAMPTZ |
Constraints:
- The combination of
entitlement_idandprovision_idis unique. - Index on
entitlement_idfor the aggregation query.
Effective limit computation: For additive stacking, the effective limit is SUM(contributed_value). For maximum, it is MAX(contributed_value). For replace, it is the contribution from the most recently activated provision.
Relationships:
numeric_entitlement_contributions(0..*) -> (1)numeric_entitlements: Contributions target a numeric entitlement.numeric_entitlement_contributions(0..*) -> (1)pool_provisions: Contributions originate from a provision.
numeric_entitlement_usage
A numeric entitlement usage record holds the mutable consumption state for a numeric entitlement, separated from the entitlement definition to eliminate write contention between the materialization pipeline (which updates definitions) and the usage increment path (which updates consumption).
| Field | Type | Purpose |
|---|---|---|
usage_id |
UUID | Primary key |
entitlement_id |
UUID | FK -> numeric_entitlements. The entitlement definition this usage tracks. |
pool_id |
UUID | FK -> resource_pools. Denormalized from entitlement for indexed lookups. |
resource_key |
VARCHAR(100) | Denormalized from entitlement for indexed lookups without join. |
current_usage |
BIGINT | Current usage against this entitlement in the current period. |
current_period_start |
TIMESTAMPTZ | Start of current reset period. NULL for limits (non-resetting). |
current_period_end |
TIMESTAMPTZ | End of current reset period. NULL for limits (non-resetting). |
last_reset_at |
TIMESTAMPTZ | When usage was last reset. |
created_at |
TIMESTAMPTZ | |
updated_at |
TIMESTAMPTZ |
Constraints:
entitlement_idis unique (1:1 with numeric_entitlements, in a separate table for write isolation).- Composite index on
(pool_id, resource_key)for the hot-path lookup.
Usage increment pattern:
UPDATE numeric_entitlement_usage
SET current_usage = current_usage + $increment
WHERE entitlement_id = $id
AND current_usage + $increment <= (SELECT resource_limit FROM numeric_entitlements WHERE entitlement_id = $id)
If no rows are returned, the limit would be exceeded.
Relationships:
numeric_entitlement_usage(1) -> (1)numeric_entitlements: Each usage record tracks one numeric entitlement.
entitlement_sets
An entitlement set is a named, reusable collection of entitlement rules -- the canonical unit of capability specification. Entitlement sets decouple "what capabilities are conferred" from "how they are sold" (products) and "why they were given" (grants). Products reference a set to declare what a purchase or subscription confers; grants may reference a set directly for ad hoc capability conferral without requiring a commercial artifact.
This factoring ensures that the materialization pipeline has a single, uniform input regardless of provisioning source: every pool provision resolves to exactly one entitlement set.
| Field | Type | Purpose |
|---|---|---|
set_id |
UUID | Primary key |
name |
VARCHAR(255) | Internal name (e.g., "Pro Capabilities", "API Access", "Board Grant — Extended Storage"). |
description |
TEXT | What this set represents and when it should be used. |
is_active |
BOOLEAN | Whether this set can be referenced by new products or grants. Existing references remain valid when deactivated. |
created_at |
TIMESTAMPTZ | |
updated_at |
TIMESTAMPTZ |
Relationships:
entitlement_sets(1) -> (0..*)entitlement_set_rules: A set contains rules that define its capabilities.entitlement_sets(1) -> (0..*)products: Products reference a set to declare what they confer.FK <- billing.products.entitlement_set_identitlement_sets(1) -> (0..*)grants: Grants may reference a set directly for ad hoc conferral.entitlement_sets(1) -> (0..*)pool_provisions: Provisions always resolve to a set for materialization.
entitlement_set_rules
An entitlement set rule defines a single capability within an entitlement set. This table uses single-table inheritance: the rule_type discriminator determines which set of fields is populated, with a CHECK constraint enforcing mutual exclusivity. Four rule types exist: boolean (binary capabilities), limit (static numeric allocations), quota (renewable consumption budgets), and credit (included prepaid credits).
Formerly product_entitlement_rules (Decision 107, Document 29). Re-parented from product_id to set_id to support the entitlement set abstraction, enabling grants to confer capabilities without requiring a product intermediary.
| Field | Type | Purpose |
|---|---|---|
rule_id |
UUID | Primary key |
set_id |
UUID | FK -> entitlement_sets. The set this rule belongs to. |
rule_type |
VARCHAR(20) | boolean, limit, quota, or credit. |
resource_key |
VARCHAR(100) | FK -> resource_keys. For boolean, limit, and quota rules: the resource identifier. NULL for credit rules. |
resource_value |
BIGINT | For limit and quota rules: the amount granted. -1 for unlimited. NULL for boolean and credit rules. |
resource_per_unit |
BOOLEAN | For limit and quota rules: whether the value is multiplied by the provision's quantity. NULL for boolean and credit rules. |
stacking_policy |
VARCHAR(20) | For limit and quota rules: how this entitlement combines with contributions from other provisions. additive (sum all contributions), maximum (take the highest), replace (most recent provision wins). NULL for boolean and credit rules. Default: additive. |
reset_period |
VARCHAR(20) | For quota rules: daily, monthly, or yearly. Required for quota rules, NULL for all other rule types. |
credit_amount |
INTEGER | For credit rules: the credit amount in smallest currency unit. NULL for all other rule types. |
credit_currency |
VARCHAR(3) | For credit rules: the currency (ISO 4217). NULL for all other rule types. |
description |
TEXT | Human-readable explanation of what this rule grants. |
is_active |
BOOLEAN | Whether this rule is currently in effect. |
created_at |
TIMESTAMPTZ | |
updated_at |
TIMESTAMPTZ |
Constraints:
CHECK (
(rule_type = 'boolean' AND resource_key IS NOT NULL
AND resource_value IS NULL AND stacking_policy IS NULL
AND reset_period IS NULL AND resource_per_unit IS NULL
AND credit_amount IS NULL AND credit_currency IS NULL)
OR (rule_type = 'limit' AND resource_key IS NOT NULL AND resource_value IS NOT NULL
AND reset_period IS NULL
AND credit_amount IS NULL AND credit_currency IS NULL)
OR (rule_type = 'quota' AND resource_key IS NOT NULL AND resource_value IS NOT NULL
AND reset_period IS NOT NULL
AND credit_amount IS NULL AND credit_currency IS NULL)
OR (rule_type = 'credit' AND credit_amount IS NOT NULL AND credit_currency IS NOT NULL
AND resource_key IS NULL AND resource_value IS NULL
AND stacking_policy IS NULL AND reset_period IS NULL AND resource_per_unit IS NULL)
)
For rule_type = 'boolean': resource_key is required; all numeric, quota, and credit fields are NULL. For rule_type = 'limit': resource_key and resource_value are required; reset_period is NULL. For rule_type = 'quota': resource_key, resource_value, and reset_period are required. For rule_type = 'credit': credit_amount and credit_currency are required; all resource fields are NULL. stacking_policy defaults to additive when NULL (applies to limit and quota rules).
Relationships:
entitlement_set_rules(0..*) -> (1)entitlement_sets: Rules belong to a set. (intra-module)entitlement_set_rules(0..*) -> (0..1)resource_keys: Boolean, limit, and quota rules reference a resource key. (intra-module)- Consumed by the materialization pipeline when pool provisions activate or deactivate, producing
boolean_entitlements,numeric_entitlements,numeric_entitlement_contributions, andcredit_grants.
resource_keys
A resource key is a canonical identifier in the shared namespace that bridges the entitlement system and the on-demand billing system (Decision 103). Both sides of the boundary — entitlement_set_rules.resource_key, boolean_entitlements.resource_key, numeric_entitlements.resource_key, pool_ondemand_config.resource_key, usage_events.resource_key — FK into this table. A typo (api-calls vs. api_calls) produces an FK violation rather than a silent namespace mismatch.
| Field | Type | Purpose |
|---|---|---|
resource_key |
VARCHAR(100) | Primary key. Machine-readable identifier (e.g., api_calls, storage_bytes, workspaces). |
display_name |
VARCHAR(255) | Human-readable name (e.g., "API Calls", "Storage"). |
description |
TEXT | What this resource represents. |
unit |
VARCHAR(50) | Unit of measurement (e.g., call, byte, seat). |
created_at |
TIMESTAMPTZ |
usage_events
A usage_event records consumption of a resource by a workspace. Usage events are append-only observational data — they record what happened, not what should be charged. All consumption paths — quota decrements, credit deductions, and on-demand charges — generate usage events. The resolution_path discriminator identifies which path resolved each event.
| Field | Type | Purpose |
|---|---|---|
event_id |
UUID | Primary key |
workspace_id |
UUID | FK -> organization.workspaces. Where consumption occurred. |
pool_id |
UUID | FK -> resource_pools. Which pool this was routed through. |
resource_key |
VARCHAR(100) | FK -> resource_keys. What was consumed (e.g., api_calls, compute_minutes). |
quantity |
BIGINT | How much was consumed (in the resource's unit). |
unit |
VARCHAR(50) | Unit of measurement (e.g., call, second, byte). |
event_timestamp |
TIMESTAMPTZ | When consumption occurred. |
resolution_path |
VARCHAR(20) | How this consumption was resolved: quota, credit, or on_demand. |
billing_account_id |
UUID | FK -> billing.billing_accounts. Resolved payer at time of event. NULL for quota-resolved events with no direct billing association. |
metadata |
JSONB | Additional event attributes. JSONB Governance Policy applies. |
created_at |
TIMESTAMPTZ |
Relationships:
usage_events(0..*) -> (1)workspaces: Events occur in a workspace.FK -> organization.workspacesusage_events(0..*) -> (1)resource_pools: Events are routed through a pool.usage_events(0..*) -> (0..1)billing_accounts: Events may have a resolved payer (NULL for quota-resolved events).FK -> billing.billing_accountsusage_events(0..) -> (0..)pending_charge_usage_events: Events may be linked to the pending charges they contributed to.FK -> billing.pending_charge_usage_eventsusage_events(0..) -> (0..)credit_transactions: Events may be linked to real-time credit deductions (prepaid topology).FK -> billing.credit_transactions