skills/cleanexpo/database-migration/SKILL.md
Guide for creating idempotent Supabase database migrations with RLS policies and workspace isolation
npx skillsauth add aiskillstore/marketplace database-migrationInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
When to Use: Adding tables, modifying schemas, creating RLS policies, adding functions
ALWAYS check before creating:
# Read schema reference
cat docs/guides/schema-reference.md
# Or check existing migrations
ls supabase/migrations/
Location: supabase/migrations/YYYYMMDDHHMMSS_description.sql
Naming: Use timestamp + descriptive name
20251230120000_add_agent_registry_table.sql
Pattern: Use IF NOT EXISTS and CREATE OR REPLACE
-- Tables
CREATE TABLE IF NOT EXISTS agent_registry (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
workspace_id UUID NOT NULL REFERENCES workspaces(id) ON DELETE CASCADE,
agent_id TEXT NOT NULL,
version TEXT NOT NULL,
capabilities JSONB NOT NULL DEFAULT '[]'::jsonb,
created_at TIMESTAMPTZ DEFAULT NOW(),
UNIQUE(workspace_id, agent_id)
);
-- Indexes
CREATE INDEX IF NOT EXISTS idx_agent_registry_workspace
ON agent_registry(workspace_id);
CREATE INDEX IF NOT EXISTS idx_agent_registry_agent
ON agent_registry(agent_id, workspace_id);
-- RLS
ALTER TABLE agent_registry ENABLE ROW LEVEL SECURITY;
DROP POLICY IF EXISTS "Users can view their workspace agents" ON agent_registry;
CREATE POLICY "Users can view their workspace agents" ON agent_registry
FOR SELECT USING (
workspace_id IN (
SELECT w.id FROM workspaces w
INNER JOIN user_organizations uo ON uo.org_id = w.org_id
WHERE uo.user_id = auth.uid()
)
);
DROP POLICY IF EXISTS "System can manage agents" ON agent_registry;
CREATE POLICY "System can manage agents" ON agent_registry
FOR ALL USING (true) WITH CHECK (true);
-- Functions
CREATE OR REPLACE FUNCTION get_agent_count(p_workspace_id UUID)
RETURNS INTEGER AS $$
BEGIN
RETURN (SELECT COUNT(*) FROM agent_registry WHERE workspace_id = p_workspace_id);
END;
$$ LANGUAGE plpgsql STABLE;
-- Comments
COMMENT ON TABLE agent_registry IS 'Registry of all active agents per workspace';
ALWAYS use: user_organizations + workspaces join (NOT workspace_members)
-- Correct pattern
workspace_id IN (
SELECT w.id FROM workspaces w
INNER JOIN user_organizations uo ON uo.org_id = w.org_id
WHERE uo.user_id = auth.uid()
)
-- For admin/owner only
workspace_id IN (
SELECT w.id FROM workspaces w
INNER JOIN user_organizations uo ON uo.org_id = w.org_id
WHERE uo.user_id = auth.uid() AND uo.role IN ('admin', 'owner')
)
Method: Supabase Dashboard → SQL Editor
Steps:
Alternative: Use WORKING_MIGRATIONS.sql pattern for combined migrations
CREATE TABLE IF NOT EXISTS my_table (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
workspace_id UUID NOT NULL REFERENCES workspaces(id) ON DELETE CASCADE,
name TEXT NOT NULL,
data JSONB,
created_at TIMESTAMPTZ DEFAULT NOW()
);
CREATE INDEX IF NOT EXISTS idx_my_table_workspace ON my_table(workspace_id);
ALTER TABLE my_table ENABLE ROW LEVEL SECURITY;
DROP POLICY IF EXISTS "workspace_isolation" ON my_table;
CREATE POLICY "workspace_isolation" ON my_table
FOR ALL USING (
workspace_id IN (
SELECT w.id FROM workspaces w
INNER JOIN user_organizations uo ON uo.org_id = w.org_id
WHERE uo.user_id = auth.uid()
)
);
DO $$ BEGIN
CREATE TYPE agent_status AS ENUM ('active', 'paused', 'disabled');
EXCEPTION WHEN duplicate_object THEN NULL;
END $$;
CREATE OR REPLACE FUNCTION update_updated_at()
RETURNS TRIGGER AS $$
BEGIN
NEW.updated_at = NOW();
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
DROP TRIGGER IF EXISTS trigger_update_updated_at ON my_table;
CREATE TRIGGER trigger_update_updated_at
BEFORE UPDATE ON my_table
FOR EACH ROW
EXECUTE FUNCTION update_updated_at();
CREATE TABLE table_name (
...
workspace_id UUID NOT NULL REFERENCES workspaces(id) ON DELETE CASCADE,
...
);
-- Always add index on workspace_id
CREATE INDEX IF NOT EXISTS idx_table_workspace ON table_name(workspace_id);
-- Always add RLS
ALTER TABLE table_name ENABLE ROW LEVEL SECURITY;
-- Check constraints
CONSTRAINT valid_status CHECK (status IN ('active', 'inactive')),
CONSTRAINT valid_score CHECK (score >= 0 AND score <= 100),
CONSTRAINT valid_email CHECK (email ~* '^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$')
-- With cascade delete
workspace_id UUID NOT NULL REFERENCES workspaces(id) ON DELETE CASCADE,
-- With set null
created_by UUID REFERENCES auth.users(id) ON DELETE SET NULL,
-- With restrict (prevents delete if referenced)
org_id UUID NOT NULL REFERENCES organizations(id) ON DELETE RESTRICT
Before applying migration:
IF NOT EXISTS on tablesCREATE OR REPLACE on functionsError: "relation workspace_members does not exist"
Fix: Use user_organizations + workspaces join (see RLS pattern above)
Error: "already exists"
Fix: Use IF NOT EXISTS or CREATE OR REPLACE
Error: "permission denied" Fix: Use service role key in Supabase Dashboard, not anon key
Standard: Idempotent, workspace-isolated, RLS-secured, well-documented
development
Apple Human Interface Guidelines for content display components. Use this skill when the user asks about charts component, collection view, image view, web view, color well, image well, activity view, lockup, data visualization, content display, displaying images, rendering web content, color pickers, or presenting collections of items in Apple apps. Also use when the user says how should I display charts, what's the best way to show images, should I use a web view, how do I build a grid of items, what component shows media, or how do I present a share sheet. Cross-references: hig-foundations for color/typography/accessibility, hig-patterns for data visualization patterns, hig-components-layout for structural containers, hig-platforms for platform-specific component behavior.
tools
Automate HelpDesk tasks via Rube MCP (Composio): list tickets, manage views, use canned responses, and configure custom fields. Always search tools first for current schemas.
testing
Expert Haskell engineer specializing in advanced type systems, pure functional design, and high-reliability software. Use PROACTIVELY for type-level programming, concurrency, and architecture guidance.
tools
GraphQL gives clients exactly the data they need - no more, no less. One endpoint, typed schema, introspection. But the flexibility that makes it powerful also makes it dangerous. Without proper controls, clients can craft queries that bring down your server. This skill covers schema design, resolvers, DataLoader for N+1 prevention, federation for microservices, and client integration with Apollo/urql. Key insight: GraphQL is a contract. The schema is the API documentation. Design it carefully.