Files
member-console/internal/entitlements/migrations/00001_init.sql
Christian Galo 1f1540d7e0 Use plain DB connection for migrations
Add ConnectPlain to open the DB without the custom search_path and
switch migration and CLI flows to run on that plain connection.
Wrap multi-statement goose migrations with StatementBegin/End to
ensure statements are executed atomically. Move Stripe price outbox
seeding into a dedicated stripe migration.
2026-04-05 18:25:05 -05:00

264 lines
13 KiB
SQL

-- +goose Up
-- +goose StatementBegin
-- Shared namespace for resource types
CREATE TABLE entitlements.resource_keys (
resource_key VARCHAR(100) PRIMARY KEY,
display_name VARCHAR(255) NOT NULL,
description TEXT,
unit VARCHAR(50) NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
-- Containers for entitlements, belonging to organizations
CREATE TABLE entitlements.resource_pools (
pool_id UUID PRIMARY KEY DEFAULT uuidv7(),
org_id UUID NOT NULL REFERENCES organization.organizations(org_id),
name VARCHAR(255) NOT NULL,
slug VARCHAR(100) NOT NULL,
pool_type VARCHAR(20) NOT NULL,
is_auto_managed BOOLEAN NOT NULL DEFAULT FALSE,
description TEXT,
status VARCHAR(20) NOT NULL DEFAULT 'active',
suspended_at TIMESTAMPTZ,
archived_at TIMESTAMPTZ,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
UNIQUE (org_id, slug)
);
-- Administrative/promotional access grants
CREATE TABLE entitlements.grants (
grant_id UUID PRIMARY KEY DEFAULT uuidv7(),
product_id UUID NOT NULL REFERENCES billing.products(product_id),
granted_to_billing_account_id UUID,
granted_to_org_id UUID REFERENCES organization.organizations(org_id),
granted_to_person_id UUID REFERENCES identity.persons(person_id),
granted_by_person_id UUID NOT NULL REFERENCES identity.persons(person_id),
grant_reason VARCHAR(50) NOT NULL,
description TEXT,
quantity INTEGER NOT NULL DEFAULT 1,
valid_from TIMESTAMPTZ NOT NULL DEFAULT NOW(),
valid_until TIMESTAMPTZ,
status VARCHAR(20) NOT NULL DEFAULT 'active',
revoked_at TIMESTAMPTZ,
revoked_by_person_id UUID REFERENCES identity.persons(person_id),
revocation_reason TEXT,
metadata JSONB,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
-- Exclusive arc: exactly one recipient must be set (D3)
CONSTRAINT chk_grants_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)
)
);
-- Uniform interface: provisions link sources to pools
CREATE TABLE entitlements.pool_provisions (
provision_id UUID PRIMARY KEY DEFAULT uuidv7(),
pool_id UUID NOT NULL REFERENCES entitlements.resource_pools(pool_id),
billing_account_id UUID,
subscription_id UUID,
purchase_id UUID,
grant_id UUID REFERENCES entitlements.grants(grant_id),
product_id UUID NOT NULL REFERENCES billing.products(product_id),
quantity INTEGER NOT NULL DEFAULT 1,
status VARCHAR(20) NOT NULL DEFAULT 'active',
activated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
suspended_at TIMESTAMPTZ,
ended_at TIMESTAMPTZ,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
-- Exclusive arc: exactly one source must be set (D4)
CONSTRAINT chk_pool_provisions_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)
)
);
-- Workspace-to-pool links
CREATE TABLE entitlements.pool_assignments (
assignment_id UUID PRIMARY KEY DEFAULT uuidv7(),
pool_id UUID NOT NULL REFERENCES entitlements.resource_pools(pool_id),
workspace_id UUID NOT NULL REFERENCES organization.workspaces(workspace_id),
is_primary BOOLEAN NOT NULL DEFAULT FALSE,
status VARCHAR(20) NOT NULL DEFAULT 'active',
suspended_at TIMESTAMPTZ,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
UNIQUE (pool_id, workspace_id)
);
-- Rules defining what products grant
CREATE TABLE entitlements.product_entitlement_rules (
rule_id UUID PRIMARY KEY DEFAULT uuidv7(),
product_id UUID NOT NULL REFERENCES billing.products(product_id),
rule_type VARCHAR(20) NOT NULL,
resource_key VARCHAR(100) REFERENCES entitlements.resource_keys(resource_key),
resource_value BIGINT,
resource_per_unit BOOLEAN,
stacking_policy VARCHAR(20) DEFAULT 'additive',
reset_period VARCHAR(20),
credit_amount INTEGER,
credit_currency VARCHAR(3),
description TEXT,
is_active BOOLEAN NOT NULL DEFAULT TRUE,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
-- Single-table inheritance constraint
CONSTRAINT chk_product_entitlement_rules_type 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)
)
);
-- Materialized numeric entitlements on a pool
CREATE TABLE entitlements.numeric_entitlements (
entitlement_id UUID PRIMARY KEY DEFAULT uuidv7(),
pool_id UUID NOT NULL REFERENCES entitlements.resource_pools(pool_id),
resource_key VARCHAR(100) NOT NULL REFERENCES entitlements.resource_keys(resource_key),
entitlement_type VARCHAR(20) NOT NULL,
resource_limit BIGINT NOT NULL DEFAULT 0,
reset_period VARCHAR(20),
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
UNIQUE (pool_id, resource_key)
);
-- Provenance: which provisions contribute to an entitlement
CREATE TABLE entitlements.numeric_entitlement_contributions (
contribution_id UUID PRIMARY KEY DEFAULT uuidv7(),
entitlement_id UUID NOT NULL REFERENCES entitlements.numeric_entitlements(entitlement_id),
provision_id UUID NOT NULL REFERENCES entitlements.pool_provisions(provision_id),
contributed_value BIGINT NOT NULL,
stacking_policy VARCHAR(20) NOT NULL DEFAULT 'additive',
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
UNIQUE (entitlement_id, provision_id)
);
-- Mutable usage state, separated for write isolation
CREATE TABLE entitlements.numeric_entitlement_usage (
usage_id UUID PRIMARY KEY DEFAULT uuidv7(),
entitlement_id UUID NOT NULL UNIQUE REFERENCES entitlements.numeric_entitlements(entitlement_id),
pool_id UUID NOT NULL REFERENCES entitlements.resource_pools(pool_id),
resource_key VARCHAR(100) NOT NULL,
current_usage BIGINT NOT NULL DEFAULT 0,
current_period_start TIMESTAMPTZ,
current_period_end TIMESTAMPTZ,
last_reset_at TIMESTAMPTZ,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
-- Indexes
CREATE INDEX idx_resource_pools_org_id ON entitlements.resource_pools(org_id);
CREATE INDEX idx_grants_org_id ON entitlements.grants(granted_to_org_id);
CREATE INDEX idx_grants_product_id ON entitlements.grants(product_id);
CREATE INDEX idx_pool_provisions_pool_id ON entitlements.pool_provisions(pool_id);
CREATE INDEX idx_pool_provisions_grant_id ON entitlements.pool_provisions(grant_id);
CREATE INDEX idx_pool_assignments_workspace_id ON entitlements.pool_assignments(workspace_id);
CREATE INDEX idx_pool_assignments_pool_id ON entitlements.pool_assignments(pool_id);
CREATE INDEX idx_product_entitlement_rules_product_id ON entitlements.product_entitlement_rules(product_id);
CREATE INDEX idx_numeric_entitlements_pool_id ON entitlements.numeric_entitlements(pool_id);
CREATE INDEX idx_numeric_entitlement_contributions_entitlement_id ON entitlements.numeric_entitlement_contributions(entitlement_id);
CREATE INDEX idx_numeric_entitlement_contributions_provision_id ON entitlements.numeric_entitlement_contributions(provision_id);
-- Hot-path composite index for entitlement checks (D11)
CREATE INDEX idx_numeric_entitlement_usage_pool_resource ON entitlements.numeric_entitlement_usage(pool_id, resource_key);
-- Updated-at triggers
CREATE TRIGGER trigger_resource_pools_updated_at
BEFORE UPDATE ON entitlements.resource_pools
FOR EACH ROW
EXECUTE FUNCTION update_updated_at_column();
CREATE TRIGGER trigger_grants_updated_at
BEFORE UPDATE ON entitlements.grants
FOR EACH ROW
EXECUTE FUNCTION update_updated_at_column();
CREATE TRIGGER trigger_pool_provisions_updated_at
BEFORE UPDATE ON entitlements.pool_provisions
FOR EACH ROW
EXECUTE FUNCTION update_updated_at_column();
CREATE TRIGGER trigger_pool_assignments_updated_at
BEFORE UPDATE ON entitlements.pool_assignments
FOR EACH ROW
EXECUTE FUNCTION update_updated_at_column();
CREATE TRIGGER trigger_product_entitlement_rules_updated_at
BEFORE UPDATE ON entitlements.product_entitlement_rules
FOR EACH ROW
EXECUTE FUNCTION update_updated_at_column();
CREATE TRIGGER trigger_numeric_entitlements_updated_at
BEFORE UPDATE ON entitlements.numeric_entitlements
FOR EACH ROW
EXECUTE FUNCTION update_updated_at_column();
CREATE TRIGGER trigger_numeric_entitlement_contributions_updated_at
BEFORE UPDATE ON entitlements.numeric_entitlement_contributions
FOR EACH ROW
EXECUTE FUNCTION update_updated_at_column();
CREATE TRIGGER trigger_numeric_entitlement_usage_updated_at
BEFORE UPDATE ON entitlements.numeric_entitlement_usage
FOR EACH ROW
EXECUTE FUNCTION update_updated_at_column();
-- Per-schema role grants (Decision 115).
GRANT ALL ON ALL TABLES IN SCHEMA entitlements TO entitlements_owner;
GRANT ALL ON ALL TABLES IN SCHEMA entitlements TO entitlements_writer;
GRANT SELECT ON ALL TABLES IN SCHEMA entitlements TO entitlements_reader;
-- +goose StatementEnd
-- +goose Down
-- +goose StatementBegin
DROP TRIGGER IF EXISTS trigger_numeric_entitlement_usage_updated_at ON entitlements.numeric_entitlement_usage;
DROP TRIGGER IF EXISTS trigger_numeric_entitlement_contributions_updated_at ON entitlements.numeric_entitlement_contributions;
DROP TRIGGER IF EXISTS trigger_numeric_entitlements_updated_at ON entitlements.numeric_entitlements;
DROP TRIGGER IF EXISTS trigger_product_entitlement_rules_updated_at ON entitlements.product_entitlement_rules;
DROP TRIGGER IF EXISTS trigger_pool_assignments_updated_at ON entitlements.pool_assignments;
DROP TRIGGER IF EXISTS trigger_pool_provisions_updated_at ON entitlements.pool_provisions;
DROP TRIGGER IF EXISTS trigger_grants_updated_at ON entitlements.grants;
DROP TRIGGER IF EXISTS trigger_resource_pools_updated_at ON entitlements.resource_pools;
DROP INDEX IF EXISTS entitlements.idx_numeric_entitlement_usage_pool_resource;
DROP INDEX IF EXISTS entitlements.idx_numeric_entitlement_contributions_provision_id;
DROP INDEX IF EXISTS entitlements.idx_numeric_entitlement_contributions_entitlement_id;
DROP INDEX IF EXISTS entitlements.idx_numeric_entitlements_pool_id;
DROP INDEX IF EXISTS entitlements.idx_product_entitlement_rules_product_id;
DROP INDEX IF EXISTS entitlements.idx_pool_assignments_pool_id;
DROP INDEX IF EXISTS entitlements.idx_pool_assignments_workspace_id;
DROP INDEX IF EXISTS entitlements.idx_pool_provisions_grant_id;
DROP INDEX IF EXISTS entitlements.idx_pool_provisions_pool_id;
DROP INDEX IF EXISTS entitlements.idx_grants_product_id;
DROP INDEX IF EXISTS entitlements.idx_grants_org_id;
DROP INDEX IF EXISTS entitlements.idx_resource_pools_org_id;
DROP TABLE IF EXISTS entitlements.numeric_entitlement_usage;
DROP TABLE IF EXISTS entitlements.numeric_entitlement_contributions;
DROP TABLE IF EXISTS entitlements.numeric_entitlements;
DROP TABLE IF EXISTS entitlements.product_entitlement_rules;
DROP TABLE IF EXISTS entitlements.pool_assignments;
DROP TABLE IF EXISTS entitlements.pool_provisions;
DROP TABLE IF EXISTS entitlements.grants;
DROP TABLE IF EXISTS entitlements.resource_pools;
DROP TABLE IF EXISTS entitlements.resource_keys;
-- +goose StatementEnd