Replace product-kind branching and direct position writes with enclosed database functions driven by structural product shape. Migrate grant and provision data, unify operator issuance, update subscription and expiry flows, and add migration and integration proofs.
431 lines
20 KiB
PL/PgSQL
431 lines
20 KiB
PL/PgSQL
-- +goose Up
|
|
|
|
-- Doc 41 (product-kind taxonomy dissolution + grant-issuance uniformity),
|
|
-- §9 steps 1-7. Steps 8-10 live in 00005 (conferral functions + enclosure)
|
|
-- and 00006 (position sweep + catalog rename). Data normalization runs
|
|
-- strictly before constraint installation; any row the mechanical bucket
|
|
-- rules cannot classify halts the migration with the offending ids rather
|
|
-- than guessing (halt-and-report). Rollback of the data steps is
|
|
-- snapshot-based; the Down section reverses schema mechanically but does
|
|
-- not resurrect dropped data.
|
|
|
|
-- ---------------------------------------------------------------------------
|
|
-- Step 1 — prices: consumption model is a per-price fact (Decision 135).
|
|
-- This schema predates usage pricing, so usage_type is added here rather
|
|
-- than normalized. A price billed per-use or per-seat only makes sense
|
|
-- against a recurring period; on-demand rate rows carry usage_type NULL and
|
|
-- are discriminated by their own mechanism, never by this flag.
|
|
-- ---------------------------------------------------------------------------
|
|
|
|
ALTER TABLE core.prices ADD COLUMN usage_type VARCHAR(20);
|
|
|
|
ALTER TABLE core.prices ADD CONSTRAINT chk_prices_usage_type_domain
|
|
CHECK (usage_type IS NULL OR usage_type IN ('metered', 'licensed'));
|
|
|
|
ALTER TABLE core.prices ADD CONSTRAINT chk_prices_usage_pricing_requires_recurrence
|
|
CHECK (usage_type IS NULL OR recurring_interval IS NOT NULL);
|
|
|
|
-- ---------------------------------------------------------------------------
|
|
-- Step 2 — provision bounds. quantity is already NOT NULL DEFAULT 1 in this
|
|
-- schema; only the positivity rule is missing.
|
|
-- ---------------------------------------------------------------------------
|
|
|
|
ALTER TABLE core.pool_provisions ADD CONSTRAINT chk_pool_provisions_quantity_positive
|
|
CHECK (quantity > 0);
|
|
|
|
-- ---------------------------------------------------------------------------
|
|
-- Steps 3+4 — grants: data normalization. Ordering within this block
|
|
-- matters and deliberately interleaves §9's steps 3 and 4: the set-direct
|
|
-- repoint (step 4 data) must run before the reason buckets and CHECKs
|
|
-- (step 3 constraints), because the bare-set grant is system-authored and
|
|
-- its 'default' reason is only trustworthy once it points at a rank-0 tier
|
|
-- product.
|
|
-- ---------------------------------------------------------------------------
|
|
|
|
-- 3a. 'trial' renames to 'evaluation' preserving semantic identity; expiry
|
|
-- stays on valid_until (Decision 139; 'trial' is a subscription/price word).
|
|
UPDATE core.grants SET grant_reason = 'evaluation' WHERE grant_reason = 'trial';
|
|
|
|
-- 4-data. Set-direct grants (product_id IS NULL): repoint to the
|
|
-- lowest-ranked tier product of the single ladder whose tier products share
|
|
-- the grant's entitlement set, and give live provisions an honest
|
|
-- retroactive ladder attachment (the pool genuinely occupied that position
|
|
-- all along). Zero or multiple candidate ladders → halt.
|
|
-- +goose StatementBegin
|
|
DO $$
|
|
DECLARE
|
|
g RECORD;
|
|
v_ladder_id UUID;
|
|
v_ladder_count INTEGER;
|
|
v_product_id UUID;
|
|
v_rank INTEGER;
|
|
prov RECORD;
|
|
v_conflict UUID;
|
|
BEGIN
|
|
FOR g IN SELECT grant_id, entitlement_set_id FROM core.grants WHERE product_id IS NULL LOOP
|
|
IF g.entitlement_set_id IS NULL THEN
|
|
RAISE EXCEPTION 'doc41 §9 step 4: grant % has neither product_id nor entitlement_set_id', g.grant_id;
|
|
END IF;
|
|
|
|
SELECT COUNT(DISTINCT t.plan_ladder_id) INTO v_ladder_count
|
|
FROM core.plan_ladder_tiers t
|
|
JOIN core.products p ON p.product_id = t.product_id
|
|
WHERE p.entitlement_set_id = g.entitlement_set_id;
|
|
|
|
IF v_ladder_count <> 1 THEN
|
|
RAISE EXCEPTION 'doc41 §9 step 4: set-direct grant % resolves to % ladders via its entitlement set; manual resolution required', g.grant_id, v_ladder_count;
|
|
END IF;
|
|
|
|
SELECT t.plan_ladder_id, t.product_id, t.rank INTO v_ladder_id, v_product_id, v_rank
|
|
FROM core.plan_ladder_tiers t
|
|
JOIN core.products p ON p.product_id = t.product_id
|
|
WHERE p.entitlement_set_id = g.entitlement_set_id
|
|
ORDER BY t.rank ASC
|
|
LIMIT 1;
|
|
|
|
UPDATE core.grants
|
|
SET product_id = v_product_id,
|
|
description = COALESCE(description || E'\n', '') ||
|
|
'[doc41 migration: set-direct grant repointed to tier product ' || v_product_id || ']'
|
|
WHERE grant_id = g.grant_id;
|
|
|
|
FOR prov IN
|
|
SELECT provision_id, pool_id, status, activated_at, ended_at
|
|
FROM core.pool_provisions
|
|
WHERE grant_id = g.grant_id AND status <> 'ended'
|
|
LOOP
|
|
-- Halt rather than collide with the GiST exclusion: an existing
|
|
-- active attachment on this (pool, ladder) means the pool held
|
|
-- two positions at once and needs a human decision.
|
|
SELECT l.provision_id INTO v_conflict
|
|
FROM core.pool_provision_ladders l
|
|
WHERE l.pool_id = prov.pool_id
|
|
AND l.plan_ladder_id = v_ladder_id
|
|
AND l.status = 'active'
|
|
LIMIT 1;
|
|
IF v_conflict IS NOT NULL THEN
|
|
RAISE EXCEPTION 'doc41 §9 step 4: retroactive attachment for grant % (provision %) conflicts with active attachment of provision % on ladder %; manual resolution required',
|
|
g.grant_id, prov.provision_id, v_conflict, v_ladder_id;
|
|
END IF;
|
|
|
|
INSERT INTO core.pool_provision_ladders
|
|
(provision_id, plan_ladder_id, pool_id, status, activated_at, ended_at, product_id)
|
|
VALUES
|
|
(prov.provision_id, v_ladder_id, prov.pool_id, prov.status, prov.activated_at, prov.ended_at, v_product_id);
|
|
|
|
INSERT INTO core.pool_provision_transitions
|
|
(pool_id, provision_id, plan_ladder_id, from_rank, to_rank, transition_type, actor_type, actor_id, reason, effective_at)
|
|
VALUES
|
|
(prov.pool_id, prov.provision_id, v_ladder_id, NULL, v_rank, 'initiate', 'system', NULL,
|
|
'doc41 §9 migration: retroactive position registration for set-direct grant', prov.activated_at);
|
|
END LOOP;
|
|
END LOOP;
|
|
END;
|
|
$$;
|
|
-- +goose StatementEnd
|
|
|
|
-- 3b. Operator-authored grants claiming 'default' are reclassified as
|
|
-- 'manual' with the claim preserved (finding #48 population).
|
|
UPDATE core.grants
|
|
SET description = COALESCE(description || E'\n', '') ||
|
|
'[doc41 migration: grant_reason was ''default'' but grant is operator-authored]',
|
|
grant_reason = 'manual'
|
|
WHERE grant_reason = 'default' AND granted_by_person_id IS NOT NULL;
|
|
|
|
-- 3c. Operator-authored grants with out-of-domain reasons (free text)
|
|
-- reclassify as 'manual', original preserved for the record.
|
|
UPDATE core.grants
|
|
SET description = COALESCE(description || E'\n', '') ||
|
|
'[doc41 migration: original grant_reason ''' || grant_reason || ''']',
|
|
grant_reason = 'manual'
|
|
WHERE grant_reason NOT IN ('manual', 'default', 'evaluation', 'promotional', 'complimentary', 'sponsored', 'board_decision', 'legacy')
|
|
AND granted_by_person_id IS NOT NULL;
|
|
|
|
-- 3d. Assertions before installing CHECKs: anything left over is a row the
|
|
-- mechanical rules cannot classify.
|
|
-- +goose StatementBegin
|
|
DO $$
|
|
DECLARE
|
|
v_ids TEXT;
|
|
BEGIN
|
|
-- System-authored rows must carry 'default' (the biconditional's other
|
|
-- half). A leftover here is a system-authored grant with a free-text or
|
|
-- non-default reason — unclassifiable mechanically.
|
|
SELECT string_agg(grant_id::text, ', ') INTO v_ids
|
|
FROM core.grants
|
|
WHERE granted_by_person_id IS NULL AND grant_reason <> 'default';
|
|
IF v_ids IS NOT NULL THEN
|
|
RAISE EXCEPTION 'doc41 §9 step 3: system-authored grants with non-default reason need manual resolution: %', v_ids;
|
|
END IF;
|
|
|
|
-- A 'default' grant is trusted as a genuine system default only if its
|
|
-- product sits at the lowest rank of some ladder (the shape the
|
|
-- automatic pipeline always produces).
|
|
SELECT string_agg(g.grant_id::text, ', ') INTO v_ids
|
|
FROM core.grants g
|
|
WHERE g.grant_reason = 'default'
|
|
AND NOT EXISTS (
|
|
SELECT 1
|
|
FROM core.plan_ladder_tiers t
|
|
WHERE t.product_id = g.product_id
|
|
AND t.rank = (SELECT MIN(t2.rank) FROM core.plan_ladder_tiers t2 WHERE t2.plan_ladder_id = t.plan_ladder_id)
|
|
);
|
|
IF v_ids IS NOT NULL THEN
|
|
RAISE EXCEPTION 'doc41 §9 step 3: ''default'' grants whose product is not a rank-0 tier need manual resolution: %', v_ids;
|
|
END IF;
|
|
END;
|
|
$$;
|
|
-- +goose StatementEnd
|
|
|
|
-- 3-constraints. grant_reason is already NOT NULL (load-bearing: both new
|
|
-- CHECKs are NULL-permeable under three-valued logic without it).
|
|
ALTER TABLE core.grants DROP CONSTRAINT chk_grants_system_actor;
|
|
|
|
ALTER TABLE core.grants ADD CONSTRAINT chk_grants_reason_domain
|
|
CHECK (grant_reason IN ('manual', 'default', 'evaluation', 'promotional', 'complimentary', 'sponsored', 'board_decision', 'legacy'));
|
|
|
|
ALTER TABLE core.grants ADD CONSTRAINT chk_grants_default_iff_system_authored
|
|
CHECK ((grant_reason = 'default') = (granted_by_person_id IS NULL));
|
|
|
|
-- 4-constraints. Every grant names a real product; the set-direct escape
|
|
-- closes structurally (Decision 137, partial reversal of Decision 112).
|
|
ALTER TABLE core.grants ALTER COLUMN product_id SET NOT NULL;
|
|
ALTER TABLE core.grants DROP CONSTRAINT chk_grants_entitlement_source;
|
|
ALTER TABLE core.grants DROP CONSTRAINT fk_grants_entitlement_set_id;
|
|
ALTER TABLE core.grants DROP COLUMN entitlement_set_id;
|
|
|
|
-- ---------------------------------------------------------------------------
|
|
-- Step 5 — provisions carry product identity. Backfill traces each
|
|
-- provision to its source arc; ambiguity halts.
|
|
-- ---------------------------------------------------------------------------
|
|
|
|
ALTER TABLE core.pool_provisions ADD COLUMN product_id UUID;
|
|
|
|
ALTER TABLE core.pool_provisions ADD CONSTRAINT fk_pool_provisions_product_id
|
|
FOREIGN KEY (product_id) REFERENCES core.products(product_id);
|
|
|
|
-- Grant-sourced: the grant's product.
|
|
UPDATE core.pool_provisions pp
|
|
SET product_id = g.product_id
|
|
FROM core.grants g
|
|
WHERE pp.grant_id = g.grant_id AND pp.product_id IS NULL;
|
|
|
|
-- Subscription-sourced: via subscription items' prices, matched on the
|
|
-- provision's entitlement set; only an unambiguous (single-product) match
|
|
-- is written.
|
|
UPDATE core.pool_provisions pp
|
|
SET product_id = m.product_id
|
|
FROM (
|
|
SELECT si.subscription_id, prod.entitlement_set_id, MIN(prod.product_id::text)::uuid AS product_id
|
|
FROM core.subscription_items si
|
|
JOIN core.prices pr ON pr.price_id = si.price_id
|
|
JOIN core.products prod ON prod.product_id = pr.product_id
|
|
GROUP BY si.subscription_id, prod.entitlement_set_id
|
|
HAVING COUNT(DISTINCT prod.product_id) = 1
|
|
) m
|
|
WHERE pp.subscription_id = m.subscription_id
|
|
AND pp.entitlement_set_id = m.entitlement_set_id
|
|
AND pp.product_id IS NULL;
|
|
|
|
-- Purchase-sourced provisions cannot exist (purchase_id is a loose slot; no
|
|
-- purchases table). Anything still NULL is ambiguous — halt-and-report.
|
|
-- +goose StatementBegin
|
|
DO $$
|
|
DECLARE
|
|
v_ids TEXT;
|
|
BEGIN
|
|
SELECT string_agg(provision_id::text, ', ') INTO v_ids
|
|
FROM core.pool_provisions WHERE product_id IS NULL;
|
|
IF v_ids IS NOT NULL THEN
|
|
RAISE EXCEPTION 'doc41 §9 step 5: provisions with ambiguous product identity need manual resolution: %', v_ids;
|
|
END IF;
|
|
|
|
-- Pre-check the per-source live-uniqueness the indexes below will
|
|
-- enforce, so the failure names rows instead of an index error.
|
|
SELECT string_agg(src, '; ') INTO v_ids FROM (
|
|
SELECT 'grant ' || grant_id::text AS src
|
|
FROM core.pool_provisions
|
|
WHERE grant_id IS NOT NULL AND status <> 'ended'
|
|
GROUP BY grant_id HAVING COUNT(*) > 1
|
|
UNION ALL
|
|
SELECT 'subscription ' || subscription_id::text || ' product ' || product_id::text
|
|
FROM core.pool_provisions
|
|
WHERE subscription_id IS NOT NULL AND status <> 'ended'
|
|
GROUP BY subscription_id, product_id HAVING COUNT(*) > 1
|
|
) dup;
|
|
IF v_ids IS NOT NULL THEN
|
|
RAISE EXCEPTION 'doc41 §9 step 5: sources with multiple live provisions need manual resolution: %', v_ids;
|
|
END IF;
|
|
END;
|
|
$$;
|
|
-- +goose StatementEnd
|
|
|
|
ALTER TABLE core.pool_provisions ALTER COLUMN product_id SET NOT NULL;
|
|
|
|
-- At most one live provision per source; status-partial so ended sources
|
|
-- can be honestly re-provisioned (reapply_defaults) and retries are
|
|
-- idempotent at the index (Invariant 6).
|
|
CREATE UNIQUE INDEX uq_pool_provisions_live_grant
|
|
ON core.pool_provisions (grant_id)
|
|
WHERE grant_id IS NOT NULL AND status <> 'ended';
|
|
|
|
CREATE UNIQUE INDEX uq_pool_provisions_live_purchase
|
|
ON core.pool_provisions (purchase_id)
|
|
WHERE purchase_id IS NOT NULL AND status <> 'ended';
|
|
|
|
CREATE UNIQUE INDEX uq_pool_provisions_live_subscription_product
|
|
ON core.pool_provisions (subscription_id, product_id)
|
|
WHERE subscription_id IS NOT NULL AND status <> 'ended';
|
|
|
|
-- ---------------------------------------------------------------------------
|
|
-- Step 6 — occupancy episodes get permanent identity (Decision 138). The
|
|
-- (provision, ladder) pair stays unique only among live rows, so a rung
|
|
-- lost and later regained is a fresh episode, never a reopened row.
|
|
-- ---------------------------------------------------------------------------
|
|
|
|
ALTER TABLE core.pool_provision_ladders ADD COLUMN provision_ladder_id UUID NOT NULL DEFAULT uuidv7();
|
|
ALTER TABLE core.pool_provision_ladders DROP CONSTRAINT pk_pool_provision_ladders;
|
|
ALTER TABLE core.pool_provision_ladders ADD CONSTRAINT pk_pool_provision_ladders PRIMARY KEY (provision_ladder_id);
|
|
|
|
CREATE UNIQUE INDEX uq_pool_provision_ladders_live
|
|
ON core.pool_provision_ladders (provision_id, plan_ladder_id)
|
|
WHERE status <> 'ended';
|
|
|
|
-- Audit rows name the specific occupancy episode. Backfill is exact: the
|
|
-- old composite PK guaranteed at most one episode per pair, so the join is
|
|
-- unambiguous. Rows predating provision attribution (provision_id IS NULL)
|
|
-- stay NULL; the three constraints below are NOT VALID — they govern new
|
|
-- writes and do not rewrite append-only history.
|
|
ALTER TABLE core.pool_provision_transitions ADD COLUMN provision_ladder_id UUID;
|
|
|
|
UPDATE core.pool_provision_transitions tr
|
|
SET provision_ladder_id = l.provision_ladder_id
|
|
FROM core.pool_provision_ladders l
|
|
WHERE tr.provision_id = l.provision_id
|
|
AND tr.plan_ladder_id = l.plan_ladder_id
|
|
AND tr.provision_id IS NOT NULL;
|
|
|
|
ALTER TABLE core.pool_provision_transitions
|
|
ADD CONSTRAINT fk_pool_provision_transitions_occupancy
|
|
FOREIGN KEY (provision_ladder_id) REFERENCES core.pool_provision_ladders(provision_ladder_id) NOT VALID;
|
|
|
|
ALTER TABLE core.pool_provision_transitions DROP CONSTRAINT chk_pool_provision_transitions_type;
|
|
ALTER TABLE core.pool_provision_transitions
|
|
ADD CONSTRAINT chk_pool_provision_transitions_type
|
|
CHECK (transition_type IN ('initiate', 'upgrade', 'downgrade', 'end', 'transfer')) NOT VALID;
|
|
|
|
ALTER TABLE core.pool_provision_transitions
|
|
ADD CONSTRAINT chk_pool_provision_transitions_occupancy_named
|
|
CHECK (provision_ladder_id IS NOT NULL) NOT VALID;
|
|
|
|
-- ---------------------------------------------------------------------------
|
|
-- Step 7 — sync trigger gains the monotonic-end guard; the conferral-shape
|
|
-- view is created. An individually-ended junction row is never reopened by
|
|
-- parent-provision updates: regaining a rung is a fresh episode.
|
|
-- ---------------------------------------------------------------------------
|
|
|
|
-- +goose StatementBegin
|
|
CREATE OR REPLACE FUNCTION core.sync_pool_provision_ladders()
|
|
RETURNS TRIGGER AS $$
|
|
BEGIN
|
|
IF NEW.pool_id IS DISTINCT FROM OLD.pool_id
|
|
OR NEW.status IS DISTINCT FROM OLD.status
|
|
OR NEW.activated_at IS DISTINCT FROM OLD.activated_at
|
|
OR NEW.ended_at IS DISTINCT FROM OLD.ended_at THEN
|
|
UPDATE core.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
|
|
-- Monotonic-end guard (Doc 41 §6.2): a deliberately-ended episode
|
|
-- stays ended; it never tracks the parent back to life.
|
|
AND status <> 'ended';
|
|
END IF;
|
|
RETURN NEW;
|
|
END;
|
|
$$ LANGUAGE plpgsql;
|
|
-- +goose StatementEnd
|
|
|
|
-- The SOLE input the conferral primitive reads (Decision 134): entitlement
|
|
-- set, lifecycle, visibility, and per-ladder (id, rank) with a ladder count.
|
|
-- Off-ladder products appear as one row with NULL ladder columns and
|
|
-- ladder_count 0.
|
|
CREATE VIEW core.product_conferral_shapes AS
|
|
SELECT
|
|
p.product_id,
|
|
p.entitlement_set_id,
|
|
p.lifecycle_status,
|
|
p.is_public,
|
|
t.plan_ladder_id,
|
|
t.rank,
|
|
COUNT(t.plan_ladder_id) OVER (PARTITION BY p.product_id) AS ladder_count
|
|
FROM core.products p
|
|
LEFT JOIN core.plan_ladder_tiers t ON t.product_id = p.product_id;
|
|
|
|
-- The init migration's GRANT ON ALL TABLES was point-in-time; new relations
|
|
-- need explicit grants.
|
|
GRANT SELECT ON core.product_conferral_shapes TO core_reader, core_writer, core_owner;
|
|
|
|
-- +goose Down
|
|
|
|
DROP VIEW core.product_conferral_shapes;
|
|
|
|
-- +goose StatementBegin
|
|
CREATE OR REPLACE FUNCTION core.sync_pool_provision_ladders()
|
|
RETURNS TRIGGER AS $$
|
|
BEGIN
|
|
IF NEW.pool_id IS DISTINCT FROM OLD.pool_id
|
|
OR NEW.status IS DISTINCT FROM OLD.status
|
|
OR NEW.activated_at IS DISTINCT FROM OLD.activated_at
|
|
OR NEW.ended_at IS DISTINCT FROM OLD.ended_at THEN
|
|
UPDATE core.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;
|
|
END IF;
|
|
RETURN NEW;
|
|
END;
|
|
$$ LANGUAGE plpgsql;
|
|
-- +goose StatementEnd
|
|
|
|
ALTER TABLE core.pool_provision_transitions DROP CONSTRAINT chk_pool_provision_transitions_occupancy_named;
|
|
ALTER TABLE core.pool_provision_transitions DROP CONSTRAINT chk_pool_provision_transitions_type;
|
|
ALTER TABLE core.pool_provision_transitions
|
|
ADD CONSTRAINT chk_pool_provision_transitions_type
|
|
CHECK (transition_type IN ('initiate', 'upgrade', 'downgrade', 'end', 'extend')) NOT VALID;
|
|
ALTER TABLE core.pool_provision_transitions DROP CONSTRAINT fk_pool_provision_transitions_occupancy;
|
|
ALTER TABLE core.pool_provision_transitions DROP COLUMN provision_ladder_id;
|
|
|
|
DROP INDEX core.uq_pool_provision_ladders_live;
|
|
ALTER TABLE core.pool_provision_ladders DROP CONSTRAINT pk_pool_provision_ladders;
|
|
ALTER TABLE core.pool_provision_ladders ADD CONSTRAINT pk_pool_provision_ladders PRIMARY KEY (provision_id, plan_ladder_id);
|
|
ALTER TABLE core.pool_provision_ladders DROP COLUMN provision_ladder_id;
|
|
|
|
DROP INDEX core.uq_pool_provisions_live_subscription_product;
|
|
DROP INDEX core.uq_pool_provisions_live_purchase;
|
|
DROP INDEX core.uq_pool_provisions_live_grant;
|
|
ALTER TABLE core.pool_provisions DROP CONSTRAINT fk_pool_provisions_product_id;
|
|
ALTER TABLE core.pool_provisions DROP COLUMN product_id;
|
|
|
|
-- Data reversals are snapshot-based (design D7): the column returns empty
|
|
-- and the pre-migration reason/product values are not resurrected.
|
|
ALTER TABLE core.grants ADD COLUMN entitlement_set_id UUID;
|
|
ALTER TABLE core.grants ADD CONSTRAINT fk_grants_entitlement_set_id
|
|
FOREIGN KEY (entitlement_set_id) REFERENCES core.entitlement_sets(set_id);
|
|
ALTER TABLE core.grants ALTER COLUMN product_id DROP NOT NULL;
|
|
ALTER TABLE core.grants ADD CONSTRAINT chk_grants_entitlement_source
|
|
CHECK (entitlement_set_id IS NOT NULL OR product_id IS NOT NULL);
|
|
ALTER TABLE core.grants DROP CONSTRAINT chk_grants_default_iff_system_authored;
|
|
ALTER TABLE core.grants DROP CONSTRAINT chk_grants_reason_domain;
|
|
ALTER TABLE core.grants ADD CONSTRAINT chk_grants_system_actor
|
|
CHECK (granted_by_person_id IS NOT NULL OR grant_reason = 'default');
|
|
|
|
ALTER TABLE core.pool_provisions DROP CONSTRAINT chk_pool_provisions_quantity_positive;
|
|
|
|
ALTER TABLE core.prices DROP CONSTRAINT chk_prices_usage_pricing_requires_recurrence;
|
|
ALTER TABLE core.prices DROP CONSTRAINT chk_prices_usage_type_domain;
|
|
ALTER TABLE core.prices DROP COLUMN usage_type;
|