-- +goose Up -- Per-member access overlay set by the company owner. -- -- Sparse JSONB map of dashboard feature key -> false. Absent key means "inherit" -- (allowed, still subject to the plan). The overlay is purely restrictive: effective -- access = plan features ∩ global gates ∩ member overlay, so a member can never be -- granted more than the company's plan allows and no privilege escalation is possible. -- -- The company owner (companies.owner_user_id) is never restricted — the owner is the -- one who edits these — so their row's overlay is ignored at resolve time. ALTER TABLE memberships ADD COLUMN IF NOT EXISTS permissions JSONB NOT NULL DEFAULT '{}'::jsonb; -- Partial index: only restricted members carry a non-empty overlay, and the common -- read is "does this member have any restriction at all". CREATE INDEX IF NOT EXISTS memberships_restricted_idx ON memberships (company_id, user_id) WHERE permissions <> '{}'::jsonb; -- +goose Down DROP INDEX IF EXISTS memberships_restricted_idx; ALTER TABLE memberships DROP COLUMN IF EXISTS permissions;