CREATE TABLE entitlements ( tenant TEXT PRIMARY KEY REFERENCES tenants(id) ON DELETE CASCADE, sso_used INTEGER NOT NULL DEFAULT 0, sso_max INTEGER NOT NULL DEFAULT 0, tenants_used INTEGER NOT NULL DEFAULT 0, tenants_max INTEGER NOT NULL DEFAULT 0, members_used INTEGER NOT NULL DEFAULT 0, members_max INTEGER NOT NULL DEFAULT 0, roles_used INTEGER NOT NULL DEFAULT 0, roles_max INTEGER NOT NULL DEFAULT 0, groups_used INTEGER NOT NULL DEFAULT 0, groups_max INTEGER NOT NULL DEFAULT 0, invitations_used INTEGER NOT NULL DEFAULT 0, invitations_max INTEGER NOT NULL DEFAULT 0, todo_lists_used INTEGER NOT NULL DEFAULT 0, todo_lists_max INTEGER NOT NULL DEFAULT 0, todo_items_used INTEGER NOT NULL DEFAULT 0, todo_items_max INTEGER NOT NULL DEFAULT 0, audit_log INTEGER NOT NULL DEFAULT 0, bots_used INTEGER NOT NULL DEFAULT 0, bots_max INTEGER NOT NULL DEFAULT 0, bot_keys_used INTEGER NOT NULL DEFAULT 0, bot_keys_max INTEGER NOT NULL DEFAULT 0, domains_used INTEGER NOT NULL DEFAULT 0, domains_max INTEGER NOT NULL DEFAULT 0 ) STRICT; -- AFTER INSERT/DELETE triggers maintain _used counters. -- BEFORE INSERT triggers enforce quotas; a missing entitlements row blocks the insert (fail-closed). -- bots CREATE TRIGGER ents_bots_incr AFTER INSERT ON bots BEGIN UPDATE entitlements SET bots_used = bots_used + 1 WHERE tenant = NEW.tenant; END; CREATE TRIGGER ents_bots_decr AFTER DELETE ON bots BEGIN UPDATE entitlements SET bots_used = bots_used - 1 WHERE tenant = OLD.tenant; END; CREATE TRIGGER quota_check_bots BEFORE INSERT ON bots BEGIN SELECT RAISE(ABORT, 'quota exceeded: bots') WHERE NOT EXISTS ( SELECT 1 FROM entitlements WHERE tenant = NEW.tenant AND bots_used < bots_max ); END; -- bot_keys CREATE TRIGGER ents_bot_keys_incr AFTER INSERT ON bot_keys BEGIN UPDATE entitlements SET bot_keys_used = bot_keys_used + 1 WHERE tenant = NEW.tenant; END; CREATE TRIGGER ents_bot_keys_decr AFTER DELETE ON bot_keys BEGIN UPDATE entitlements SET bot_keys_used = bot_keys_used - 1 WHERE tenant = OLD.tenant; END; CREATE TRIGGER quota_check_bot_keys BEFORE INSERT ON bot_keys BEGIN SELECT RAISE(ABORT, 'quota exceeded: bot_keys') WHERE NOT EXISTS ( SELECT 1 FROM entitlements WHERE tenant = NEW.tenant AND bot_keys_used < bot_keys_max ); END; -- groups CREATE TRIGGER ents_groups_incr AFTER INSERT ON groups BEGIN UPDATE entitlements SET groups_used = groups_used + 1 WHERE tenant = NEW.tenant; END; CREATE TRIGGER ents_groups_decr AFTER DELETE ON groups BEGIN UPDATE entitlements SET groups_used = groups_used - 1 WHERE tenant = OLD.tenant; END; CREATE TRIGGER quota_check_groups BEFORE INSERT ON groups BEGIN SELECT RAISE(ABORT, 'quota exceeded: groups') WHERE NOT EXISTS ( SELECT 1 FROM entitlements WHERE tenant = NEW.tenant AND groups_used < groups_max ); END; -- roles CREATE TRIGGER ents_roles_incr AFTER INSERT ON roles BEGIN UPDATE entitlements SET roles_used = roles_used + 1 WHERE tenant = NEW.tenant; END; CREATE TRIGGER ents_roles_decr AFTER DELETE ON roles BEGIN UPDATE entitlements SET roles_used = roles_used - 1 WHERE tenant = OLD.tenant; END; CREATE TRIGGER quota_check_roles BEFORE INSERT ON roles BEGIN SELECT RAISE(ABORT, 'quota exceeded: roles') WHERE NOT EXISTS ( SELECT 1 FROM entitlements WHERE tenant = NEW.tenant AND roles_used < roles_max ); END; -- members (user grants only, non-system) CREATE TRIGGER ents_members_incr AFTER INSERT ON grants WHEN NEW.type = 'user' AND NEW.system = 0 BEGIN UPDATE entitlements SET members_used = members_used + 1 WHERE tenant = NEW.tenant; END; CREATE TRIGGER ents_members_decr AFTER DELETE ON grants WHEN OLD.type = 'user' AND OLD.system = 0 BEGIN UPDATE entitlements SET members_used = members_used - 1 WHERE tenant = OLD.tenant; END; -- WHEN guard skips the check if the grant already exists so that DO NOTHING -- upserts don't falsely fail when the quota is full. CREATE TRIGGER quota_check_members BEFORE INSERT ON grants WHEN NEW.type = 'user' AND NEW.system = 0 AND NOT EXISTS ( SELECT 1 FROM grants WHERE tenant = NEW.tenant AND type = NEW.type AND principal = NEW.principal AND role = NEW.role AND path = NEW.path ) BEGIN SELECT RAISE(ABORT, 'quota exceeded: members') WHERE NOT EXISTS ( SELECT 1 FROM entitlements WHERE tenant = NEW.tenant AND members_used < members_max ); END; -- tenant_invitations CREATE TRIGGER ents_invitations_incr AFTER INSERT ON tenant_invitations BEGIN UPDATE entitlements SET invitations_used = invitations_used + 1 WHERE tenant = NEW.tenant; END; CREATE TRIGGER ents_invitations_decr AFTER DELETE ON tenant_invitations BEGIN UPDATE entitlements SET invitations_used = invitations_used - 1 WHERE tenant = OLD.tenant; END; CREATE TRIGGER quota_check_invitations BEFORE INSERT ON tenant_invitations BEGIN SELECT RAISE(ABORT, 'quota exceeded: invitations') WHERE NOT EXISTS ( SELECT 1 FROM entitlements WHERE tenant = NEW.tenant AND invitations_used < invitations_max ); END; -- todo_lists CREATE TRIGGER ents_todo_lists_incr AFTER INSERT ON todo_lists BEGIN UPDATE entitlements SET todo_lists_used = todo_lists_used + 1 WHERE tenant = NEW.tenant; END; CREATE TRIGGER ents_todo_lists_decr AFTER DELETE ON todo_lists BEGIN UPDATE entitlements SET todo_lists_used = todo_lists_used - 1 WHERE tenant = OLD.tenant; END; CREATE TRIGGER quota_check_todo_lists BEFORE INSERT ON todo_lists BEGIN SELECT RAISE(ABORT, 'quota exceeded: todo_lists') WHERE NOT EXISTS ( SELECT 1 FROM entitlements WHERE tenant = NEW.tenant AND todo_lists_used < todo_lists_max ); END; -- todo_items CREATE TRIGGER ents_todo_items_incr AFTER INSERT ON todo_items BEGIN UPDATE entitlements SET todo_items_used = todo_items_used + 1 WHERE tenant = NEW.tenant; END; CREATE TRIGGER ents_todo_items_decr AFTER DELETE ON todo_items BEGIN UPDATE entitlements SET todo_items_used = todo_items_used - 1 WHERE tenant = OLD.tenant; END; CREATE TRIGGER quota_check_todo_items BEFORE INSERT ON todo_items BEGIN SELECT RAISE(ABORT, 'quota exceeded: todo_items') WHERE NOT EXISTS ( SELECT 1 FROM entitlements WHERE tenant = NEW.tenant AND todo_items_used < todo_items_max ); END; -- domains CREATE TRIGGER ents_domains_incr AFTER INSERT ON domains BEGIN UPDATE entitlements SET domains_used = domains_used + 1 WHERE tenant = NEW.tenant; END; CREATE TRIGGER ents_domains_decr AFTER DELETE ON domains BEGIN UPDATE entitlements SET domains_used = domains_used - 1 WHERE tenant = OLD.tenant; END; CREATE TRIGGER quota_check_domains BEFORE INSERT ON domains BEGIN SELECT RAISE(ABORT, 'quota exceeded: domains') WHERE NOT EXISTS ( SELECT 1 FROM entitlements WHERE tenant = NEW.tenant AND domains_used < domains_max ); END; -- sso_configs CREATE TRIGGER ents_sso_incr AFTER INSERT ON sso_configs BEGIN UPDATE entitlements SET sso_used = sso_used + 1 WHERE tenant = NEW.tenant; END; CREATE TRIGGER ents_sso_decr AFTER DELETE ON sso_configs BEGIN UPDATE entitlements SET sso_used = sso_used - 1 WHERE tenant = OLD.tenant; END; -- Save is an upsert (ON CONFLICT DO UPDATE), so BEFORE INSERT fires on updates too. -- The WHEN guard skips the quota check when the config already exists. CREATE TRIGGER quota_check_sso BEFORE INSERT ON sso_configs WHEN NOT EXISTS (SELECT 1 FROM sso_configs WHERE id = NEW.id) BEGIN SELECT RAISE(ABORT, 'quota exceeded: sso') WHERE NOT EXISTS ( SELECT 1 FROM entitlements WHERE tenant = NEW.tenant AND sso_used < sso_max ); END;