DB Enforcer Overview Enforces data integrity and architectural consistency between the TypeScript application layer and the PostgreSQL persistence layer. Prevents type drift by ensuring CHECK constraints mirror TypeScript types, migrations are generated before applying changes, and Row-Level Security protects every table. When to use: Schema design, migration planning, RLS policy authoring, Prisma model mapping, constraint auditing, zero-downtime deployments. When NOT to use: Application-level business logic, frontend state management, non-PostgreSQL databases. For full RLS auditing, performa…

);\n END IF;\nEND $;\n```\n\nFor enum types, check existence before creating:\n\n```sql\nDO $\nBEGIN\n IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'user_status') THEN\n CREATE TYPE user_status AS ENUM ('active', 'suspended', 'deleted');\n END IF;\nEND $;\n```\n\n## Lock Management\n\n### Set a Lock Timeout\n\nAlways set a lock timeout before DDL statements to prevent indefinite blocking:\n\n```sql\nSET lock_timeout = '2s';\nALTER TABLE users ADD COLUMN last_seen_at TIMESTAMPTZ;\n```\n\nIf the lock cannot be acquired within the timeout, the statement fails immediately rather than blocking all queries on that table.\n\n### Avoid Long-Running Transactions During DDL\n\nDDL inside a long transaction holds locks for the entire transaction duration. Keep DDL statements in short, isolated transactions:\n\n```sql\nBEGIN;\nSET lock_timeout = '2s';\nALTER TABLE orders ADD COLUMN IF NOT EXISTS fulfilled_at TIMESTAMPTZ;\nCOMMIT;\n```\n\nNever combine DDL with bulk DML in the same transaction on high-traffic tables.\n\n### CREATE INDEX CONCURRENTLY\n\n`CREATE INDEX CONCURRENTLY` builds the index without holding a lock on the table, allowing reads and writes to continue. It cannot run inside a transaction block.\n\n```sql\nCREATE INDEX CONCURRENTLY IF NOT EXISTS idx_orders_user_id\n ON orders (user_id);\n```\n\nIn Prisma raw migrations, wrap concurrent index creation outside transactions:\n\n```sql\n-- This file must not be wrapped in BEGIN/COMMIT by the migration runner\nCREATE INDEX CONCURRENTLY IF NOT EXISTS idx_orders_status\n ON orders (status)\n WHERE status != 'completed';\n```\n\nIf a concurrent index build fails partway through, it leaves an invalid index. Clean it up before retrying:\n\n```sql\nDROP INDEX CONCURRENTLY IF EXISTS idx_orders_status;\n```\n\n### NOT NULL Addition on Large Tables\n\n```sql\n-- Step 1: Add nullable column\nALTER TABLE users ADD COLUMN display_name TEXT;\n\n-- Step 2: Backfill (run as a batch job)\nUPDATE users SET display_name = name WHERE display_name IS NULL;\n\n-- Step 3: Add constraint without full lock\nALTER TABLE users ADD CONSTRAINT users_display_name_not_null\n CHECK (display_name IS NOT NULL) NOT VALID;\n\n-- Step 4: Validate separately\nALTER TABLE users VALIDATE CONSTRAINT users_display_name_not_null;\n```\n\n## Backfill Patterns\n\n### Batched UPDATE with LIMIT\n\nBackfilling millions of rows in a single UPDATE locks the table and risks transaction log exhaustion. Use cursor-based batching instead:\n\n```sql\nDO $\nDECLARE\n batch_size INT := 1000;\n last_id UUID := '00000000-0000-0000-0000-000000000000';\n rows_updated INT;\nBEGIN\n LOOP\n UPDATE users\n SET display_name = name\n WHERE id > last_id\n AND display_name IS NULL\n AND id IN (\n SELECT id FROM users\n WHERE id > last_id\n AND display_name IS NULL\n ORDER BY id\n LIMIT batch_size\n )\n RETURNING id INTO last_id;\n\n GET DIAGNOSTICS rows_updated = ROW_COUNT;\n EXIT WHEN rows_updated = 0;\n\n PERFORM pg_sleep(0.05);\n END LOOP;\nEND $;\n```\n\n### Monitoring Backfill Progress\n\nTrack progress without interrupting the backfill:\n\n```sql\nSELECT\n COUNT(*) FILTER (WHERE display_name IS NOT NULL) AS backfilled,\n COUNT(*) FILTER (WHERE display_name IS NULL) AS remaining,\n COUNT(*) AS total,\n ROUND(\n 100.0 * COUNT(*) FILTER (WHERE display_name IS NOT NULL) / COUNT(*),\n 2\n ) AS pct_complete\nFROM users;\n```\n\n### Avoiding Table Locks During Backfill\n\n- Use small batch sizes (500–2000 rows) with short sleeps between batches\n- Target rows by primary key range, not offset, to avoid full scans\n- Run backfill during low-traffic windows when possible\n- Never wrap the entire backfill in a single transaction\n\n## CI/CD Integration\n\n### Pre-Deploy vs Post-Deploy Migrations\n\nClassify each migration before running it in the pipeline:\n\n| Migration type | When to run | Why |\n| ------------------------------ | ----------- | ------------------------------------------------- |\n| Add nullable column | Pre-deploy | Safe to apply before new code reads it |\n| Add index (`CONCURRENTLY`) | Pre-deploy | No lock; new code benefits immediately |\n| Add NOT NULL constraint | Post-deploy | Requires backfill to complete first |\n| Drop column | Post-deploy | Old code must be retired before column is removed |\n| Rename column (expand phase) | Pre-deploy | Add new column before code writes to it |\n| Rename column (contract phase) | Post-deploy | Remove old column after all reads have switched |\n\n### Running Migrations in the Pipeline\n\n```bash\n# Verify schema is in sync before deploying\nnpx prisma validate\n\n# Apply pending migrations (non-interactive, safe for CI)\nnpx prisma migrate deploy\n\n# Run post-deploy migrations separately after smoke tests pass\nnpx prisma migrate deploy --schema=prisma/post-deploy.prisma\n```\n\nFor raw SQL migrations outside Prisma:\n\n```bash\npsql \"$DATABASE_URL\" \\\n --set ON_ERROR_STOP=1 \\\n --single-transaction \\\n -f db/migrations/042_add_fulfilled_at.sql\n```\n\nUse `--single-transaction` for DDL-only migrations where atomicity is safe. Omit it when the migration contains `CREATE INDEX CONCURRENTLY` (which cannot run inside a transaction).\n\n### Deployment Rollback\n\nEvery migration must have a documented rollback. Store rollback scripts alongside forward migrations:\n\n```text\ndb/migrations/042_add_fulfilled_at.sql\ndb/migrations/042_add_fulfilled_at.rollback.sql\n```\n\nFor irreversible changes (data deletion, column removal), verify a point-in-time backup exists before running the migration.\n\n## Numbered Migration Standard\n\nUse 3-digit numbered sequences for clarity and ordering:\n\n```text\ndb/migrations/001_initial_schema.sql\ndb/migrations/002_add_roles.sql\ndb/migrations/003_add_team_permissions.sql\n```\n\n## Rollback Strategy\n\nEvery migration must have a corresponding rollback plan documented, even if not automated. For irreversible changes (data deletion, column removal), ensure data is backed up before execution.\n","content_type":"text/markdown; charset=utf-8","language":"markdown","size":8698,"content_sha256":"41c61d997d13997610b2c9d530e001e42b7da8164f136fe327ffc161871ed0bd"},{"filename":"references/postgres-integrity.md","content":"---\ntitle: PostgreSQL Integrity\ndescription: Advanced PostgreSQL constraints, UUIDv7 primary keys, virtual columns, temporal uniqueness, and NOT VALID constraint patterns\ntags:\n [\n postgresql,\n uuidv7,\n constraints,\n virtual-columns,\n temporal,\n check-constraints,\n ]\n---\n\n# PostgreSQL Integrity\n\n## Native UUIDv7 Support (PostgreSQL 18+)\n\nPostgreSQL 18 introduces the native `uuidv7()` function (RFC 9562). This is the preferred primary key format, combining global uniqueness with sequential ordering for significantly improved B-tree index performance and reduced page splits. The implementation includes a 12-bit sub-millisecond timestamp fraction that guarantees monotonicity within a session.\n\n```sql\nCREATE TABLE users (\n id UUID PRIMARY KEY DEFAULT uuidv7(),\n email TEXT UNIQUE NOT NULL\n);\n```\n\nFor PostgreSQL versions before 18, use the `pgcrypto` extension with `gen_random_uuid()` (UUIDv4) or install a third-party extension for UUIDv7 support.\n\n**Security note:** UUIDv7 embeds a 48-bit timestamp, leaking creation time. Avoid exposing UUIDv7 primary keys in public-facing APIs where creation time is sensitive.\n\n## Virtual Generated Columns (PostgreSQL 18+)\n\nPostgreSQL 18 introduces virtual generated columns that occupy zero disk space and are calculated on the fly during SELECT. Virtual is the default kind in PostgreSQL 18; the `VIRTUAL` keyword is optional.\n\n```sql\nCREATE TABLE products (\n price_cents INTEGER NOT NULL,\n tax_rate DECIMAL NOT NULL,\n -- VIRTUAL is default in PG 18, STORED writes to disk\n total_price_cents INTEGER GENERATED ALWAYS AS (price_cents * (1 + tax_rate)) VIRTUAL\n);\n```\n\n**Limitations of virtual columns:** Cannot be indexed (indexing support planned for PostgreSQL 19), cannot use user-defined types or functions in the generation expression, and cannot be logically replicated.\n\nFor PostgreSQL versions before 18, only `STORED` generated columns are available.\n\n## Advanced CHECK Constraints\n\nEnforce business logic at the database level. CHECK constraints mirror TypeScript types to prevent drift.\n\n```sql\nALTER TABLE orders\nADD CONSTRAINT check_discount_logic\nCHECK (discount_price \u003c original_price);\n```\n\nConditional constraints for enums ensure data consistency:\n\n```sql\nALTER TABLE tasks\nADD CONSTRAINT check_completion_date\nCHECK (\n (status = 'COMPLETED' AND completed_at IS NOT NULL) OR\n (status != 'COMPLETED' AND completed_at IS NULL)\n);\n```\n\n## Temporal Constraints\n\nDefine uniqueness over time ranges to prevent overlapping schedules or double-bookings natively.\n\n```sql\nCREATE TABLE bookings (\n room_id INTEGER,\n booking_period TSTZRANGE,\n EXCLUDE USING gist (room_id WITH =, booking_period WITH &&)\n);\n```\n\n## NOT VALID Constraint Pattern\n\nAdd constraints to large tables without locking the database for hours. This is a two-step process:\n\n1. Add as NOT VALID (takes a brief lock, does not scan existing rows):\n\n```sql\nALTER TABLE logs\nADD CONSTRAINT check_level\nCHECK (level IN ('INFO', 'WARN', 'ERROR')) NOT VALID;\n```\n\n2. Validate later (scans rows but only takes a SHARE UPDATE EXCLUSIVE lock):\n\n```sql\nALTER TABLE logs VALIDATE CONSTRAINT check_level;\n```\n","content_type":"text/markdown; charset=utf-8","language":"markdown","size":3154,"content_sha256":"164a5443907e068ab1e349a89e62af8cc6f0aebb90f3712d1b4180f83177894d"},{"filename":"references/prisma-architecture.md","content":"---\ntitle: Prisma Architecture\ndescription: Prisma TypedSQL, relation mode, extensions for soft deletes and audit logs, edge-first query engine, and distinct operations\ntags: [prisma, typedsql, extensions, edge, relation-mode, soft-delete]\n---\n\n# Prisma Architecture\n\n## TypedSQL\n\nReplace raw SQL strings with `.sql` files that generate fully typed functions. TypedSQL provides type-safe inputs and outputs while preserving full SQL flexibility.\n\nWorkflow:\n\n1. Create a `.sql` file (the filename must be a valid JS identifier and cannot start with ` db-enforcer — Skillopedia ):\n\n```sql\n-- prisma/sql/get_active_users.sql\nSELECT u.id, u.name, COUNT(p.id) as \"postCount\"\nFROM \"User\" u\nLEFT JOIN \"Post\" p ON u.id = p.\"authorId\"\nGROUP BY u.id, u.name\n```\n\n2. Run `prisma generate` to produce typed functions\n3. Import and execute with full type safety:\n\n```ts\nimport { PrismaClient } from './generated/prisma/client';\nimport { getActiveUsers } from './generated/prisma/sql';\n\nconst prisma = new PrismaClient();\nconst users = await prisma.$queryRawTyped(getActiveUsers());\n```\n\nParameters are passed as typed function arguments:\n\n```ts\nimport { getUsersByAge } from './generated/prisma/sql';\n\nconst users = await prisma.$queryRawTyped(getUsersByAge(18, 30));\n```\n\n## Relation Mode (Emulated Integrity)\n\nIn environments that do not support foreign keys (PlanetScale, certain Vitess setups), use emulated relation mode. GA since Prisma 4.8.0.\n\nTwo modes are available:\n\n- `\"foreignKeys\"` (default for relational databases) -- uses database-level foreign keys\n- `\"prisma\"` -- emulates referential integrity in Prisma Client with additional queries\n\n```prisma\ndatasource db {\n provider = \"mysql\"\n url = env(\"DATABASE_URL\")\n relationMode = \"prisma\"\n}\n```\n\nYou MUST manually create indices for all scalar fields used in relations. Without foreign keys, the database does not auto-create these indices, leading to full table scans:\n\n```prisma\nmodel Post {\n id Int @id @default(autoincrement())\n authorId Int\n author User @relation(fields: [authorId], references: [id])\n\n @@index([authorId])\n}\n```\n\n**Performance note:** Emulated mode uses additional queries per operation to maintain integrity. Prefer native foreign keys when the database supports them.\n\n## Extensions for Cross-Cutting Concerns\n\nUse Prisma Extensions for soft deletes, automatic auditing, and other middleware patterns:\n\n```ts\nconst prisma = new PrismaClient().$extends({\n model: {\n user: {\n async softDelete(id: string) {\n return prisma.user.update({\n where: { id },\n data: { deletedAt: new Date() },\n });\n },\n },\n },\n});\n```\n\n## Edge-First Query Engine\n\nPrisma uses the TypeScript/WASM engine by default, eliminating the need for bulky Rust binaries in Edge Functions. Ensure `prisma generate` is run with the correct engine target for your deployment platform (Vercel, Cloudflare).\n\n## Native Distinct and Skip Scan\n\nUse PostgreSQL performance improvements with native Prisma filters:\n\n```ts\nconst uniqueUsers = await prisma.user.findMany({\n distinct: ['email'],\n take: 10,\n});\n```\n","content_type":"text/markdown; charset=utf-8","language":"markdown","size":3098,"content_sha256":"6e5914531bd79bf614e87c7a924ce1c68d5717b24e87bace875d27f32fd35332"},{"filename":"references/rls-security.md","content":"---\ntitle: RLS and Security\ndescription: Row-Level Security policies, column-level security via views, security definer vs invoker, and temporal audit logging\ntags:\n [\n rls,\n row-level-security,\n column-level-security,\n audit-logging,\n postgresql-views,\n ]\n---\n\n# RLS and Security\n\n## Enabling RLS\n\nEvery table in a Supabase or Neon project MUST have RLS enabled:\n\n```sql\nALTER TABLE projects ENABLE ROW LEVEL SECURITY;\n```\n\n## Standard Policy Patterns\n\n### Own-Data Access\n\nThe default policy for personal data:\n\n```sql\nCREATE POLICY \"Users can manage their own projects\"\nON projects\nFOR ALL\nUSING (auth.uid() = user_id);\n```\n\n### Team-Based Access\n\nUse EXISTS subqueries for permission checks via join tables:\n\n```sql\nCREATE POLICY \"Team members can view shared data\"\nON team_data\nFOR SELECT\nUSING (\n EXISTS (\n SELECT 1 FROM team_members\n WHERE team_members.team_id = team_data.team_id\n AND team_members.user_id = auth.uid()\n )\n);\n```\n\n## Security Definers vs Security Invokers\n\n- **Definer**: Function runs with the owner's privileges. Use sparingly and only for trusted administrative operations.\n- **Invoker**: Function runs with the caller's privileges. Recommended for API integration where RLS should still apply.\n\n## Column-Level Security (CLS)\n\nUse PostgreSQL Views to hide sensitive columns from public APIs:\n\n```sql\nCREATE VIEW public_user_profiles AS\nSELECT id, name, avatar_url\nFROM users\nWHERE is_public = true;\n```\n\nThis prevents exposure of password hashes, internal IDs, or other sensitive fields through the public-facing API layer.\n\n## Audit Logging with Trigger-Based History\n\nPostgreSQL does not have native SQL:2011 system versioning (SYSTEM_TIME periods). Use trigger-based audit logging to maintain a complete history of all changes:\n\n```sql\nCREATE TABLE sensitive_data_history (\n history_id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,\n operation TEXT NOT NULL CHECK (operation IN ('INSERT', 'UPDATE', 'DELETE')),\n changed_at TIMESTAMPTZ NOT NULL DEFAULT now(),\n changed_by TEXT NOT NULL DEFAULT current_user,\n row_data JSONB NOT NULL\n);\n\nCREATE OR REPLACE FUNCTION audit_trigger()\nRETURNS TRIGGER AS $\nBEGIN\n IF TG_OP = 'DELETE' THEN\n INSERT INTO sensitive_data_history (operation, row_data)\n VALUES (TG_OP, to_jsonb(OLD));\n RETURN OLD;\n ELSE\n INSERT INTO sensitive_data_history (operation, row_data)\n VALUES (TG_OP, to_jsonb(NEW));\n RETURN NEW;\n END IF;\nEND;\n$ LANGUAGE plpgsql;\n\nCREATE TRIGGER sensitive_data_audit\nAFTER INSERT OR UPDATE OR DELETE ON sensitive_data\nFOR EACH ROW EXECUTE FUNCTION audit_trigger();\n```\n\nFor Supabase projects, the `database-security` skill covers PGAudit configuration and advanced audit trail patterns in more detail.\n","content_type":"text/markdown; charset=utf-8","language":"markdown","size":2739,"content_sha256":"743327614a7b34b790efaa3324c7453684e1fb98b1d68172398b05278711807d"}],"content_json":{"type":"doc","content":[{"type":"heading","attrs":{"level":1},"content":[{"text":"DB Enforcer","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Overview","type":"text"}]},{"type":"paragraph","content":[{"text":"Enforces data integrity and architectural consistency between the TypeScript application layer and the PostgreSQL persistence layer. Prevents type drift by ensuring CHECK constraints mirror TypeScript types, migrations are generated before applying changes, and Row-Level Security protects every table.","type":"text"}]},{"type":"paragraph","content":[{"text":"When to use:","type":"text","marks":[{"type":"strong"}]},{"text":" Schema design, migration planning, RLS policy authoring, Prisma model mapping, constraint auditing, zero-downtime deployments.","type":"text"}]},{"type":"paragraph","content":[{"text":"When NOT to use:","type":"text","marks":[{"type":"strong"}]},{"text":" Application-level business logic, frontend state management, non-PostgreSQL databases. For full RLS auditing, performance tuning, and compliance validation, use the ","type":"text"},{"text":"database-security","type":"text","marks":[{"type":"code_inline"}]},{"text":" skill instead.","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Quick Reference","type":"text"}]},{"type":"table","attrs":{"layout":null},"content":[{"type":"tr","content":[{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Pattern","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"API/Tool","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Key Points","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Type-to-DB sync","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"prisma migrate dev --create-only","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Generate SQL before applying changes","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Naming alignment","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"@map","type":"text","marks":[{"type":"code_inline"}]},{"text":" / ","type":"text"},{"text":"@@map","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"snake_case in SQL, camelCase in TS","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Primary keys","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"DEFAULT uuidv7()","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Sequential, globally unique, fast indexing (PG 18+)","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Virtual columns","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"GENERATED ALWAYS AS (...) VIRTUAL","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Zero disk cost, computed on read (PG 18+)","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Temporal uniqueness","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"EXCLUDE USING gist","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Prevent overlapping ranges natively","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"NOT VALID constraints","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"ADD CONSTRAINT ... NOT VALID","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Add constraints without table locks","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"TypedSQL","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"prisma.$queryRawTyped()","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Type-safe raw SQL via ","type":"text"},{"text":".sql","type":"text","marks":[{"type":"code_inline"}]},{"text":" files","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Relation emulation","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"relationMode = \"prisma\"","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Integrity in FK-less environments (GA since 4.8.0)","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Soft deletes","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Prisma ","type":"text"},{"text":"$extends","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Cross-cutting concern via client extensions","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"RLS standard","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"(select auth.uid()) = user_id","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Default own-data access policy with initPlan caching","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Team RLS","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"EXISTS","type":"text","marks":[{"type":"code_inline"}]},{"text":" subquery","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Permission checks via join tables","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Column-level security","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"PostgreSQL Views","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Hide sensitive columns from public APIs","type":"text"}]}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Synchronization Protocol","type":"text"}]},{"type":"paragraph","content":[{"text":"Every schema modification MUST follow these steps:","type":"text"}]},{"type":"ordered_list","attrs":{"order":1,"listStyle":"number"},"content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Type-to-DB Verification","type":"text","marks":[{"type":"strong"}]},{"text":": When adding an enum or union in TS, verify the equivalent CHECK constraint in SQL","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Migration-First Generation","type":"text","marks":[{"type":"strong"}]},{"text":": Generate SQL migrations using ","type":"text"},{"text":"prisma migrate dev --create-only","type":"text","marks":[{"type":"code_inline"}]},{"text":" BEFORE applying","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Naming Alignment","type":"text","marks":[{"type":"strong"}]},{"text":": Enforce snake_case in SQL and camelCase in TS via explicit ","type":"text"},{"text":"@map","type":"text","marks":[{"type":"code_inline"}]},{"text":"/","type":"text"},{"text":"@@map","type":"text","marks":[{"type":"code_inline"}]},{"text":" directives","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Integrity Audit","type":"text","marks":[{"type":"strong"}]},{"text":": Run ","type":"text"},{"text":"prisma validate","type":"text","marks":[{"type":"code_inline"}]},{"text":" and check for missing indices on relation scalars","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"RLS Verification","type":"text","marks":[{"type":"strong"}]},{"text":": Confirm every new table has RLS enabled with appropriate policies","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Lock Assessment","type":"text","marks":[{"type":"strong"}]},{"text":": Evaluate whether migration requires ","type":"text"},{"text":"CREATE INDEX CONCURRENTLY","type":"text","marks":[{"type":"code_inline"}]},{"text":" or ","type":"text"},{"text":"NOT VALID","type":"text","marks":[{"type":"code_inline"}]},{"text":" patterns","type":"text"}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"PostgreSQL Version Requirements","type":"text"}]},{"type":"paragraph","content":[{"text":"Several patterns in this skill require specific PostgreSQL versions:","type":"text"}]},{"type":"table","attrs":{"layout":null},"content":[{"type":"tr","content":[{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Feature","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Minimum Version","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Fallback","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"uuidv7()","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"PostgreSQL 18","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"gen_random_uuid()","type":"text","marks":[{"type":"code_inline"}]},{"text":" (UUIDv4) via ","type":"text"},{"text":"pgcrypto","type":"text","marks":[{"type":"code_inline"}]}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Virtual columns","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"PostgreSQL 18","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"STORED","type":"text","marks":[{"type":"code_inline"}]},{"text":" generated columns (PG 12+)","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"EXCLUDE USING","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"PostgreSQL 9.0","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Application-level overlap checks","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"NOT VALID","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"PostgreSQL 9.1","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Schedule constraint addition during downtime","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"security_invoker","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"PostgreSQL 15","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Use ","type":"text"},{"text":"security_definer","type":"text","marks":[{"type":"code_inline"}]},{"text":" with restricted grants","type":"text"}]}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Common Mistakes","type":"text"}]},{"type":"table","attrs":{"layout":null},"content":[{"type":"tr","content":[{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Mistake","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Correct Pattern","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Running SQL changes manually without migrations","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Generate numbered migrations with ","type":"text"},{"text":"prisma migrate dev --create-only","type":"text","marks":[{"type":"code_inline"}]},{"text":" before applying","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Using auto-increment or raw IDs exposed in URLs","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Use UUIDv7 for globally unique, non-enumerable identifiers","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Skipping CHECK constraints on enums or unions","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Add database-level CHECK constraints that mirror TypeScript types","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Mixing snake_case and camelCase without explicit mapping","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Use ","type":"text"},{"text":"@map","type":"text","marks":[{"type":"code_inline"}]},{"text":" and ","type":"text"},{"text":"@@map","type":"text","marks":[{"type":"code_inline"}]},{"text":" to enforce snake_case in SQL and camelCase in TypeScript","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Tables without Row-Level Security policies","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Apply RLS policies to every table, defaulting to ","type":"text"},{"text":"(select auth.uid()) = user_id","type":"text","marks":[{"type":"code_inline"}]}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"DROP or RENAME column in a single deployment","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Use expand-and-contract: add new column, dual-write, backfill, switch reads, drop old","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Adding NOT NULL to large tables with full lock","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Add column as NULL first, backfill, then add NOT NULL with NOT VALID","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Creating indices without CONCURRENTLY","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Use ","type":"text"},{"text":"CREATE INDEX CONCURRENTLY","type":"text","marks":[{"type":"code_inline"}]},{"text":" in raw SQL migrations to avoid table locks","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Using ","type":"text"},{"text":"auth.uid()","type":"text","marks":[{"type":"code_inline"}]},{"text":" directly in RLS without subselect","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Wrap in ","type":"text"},{"text":"(select auth.uid())","type":"text","marks":[{"type":"code_inline"}]},{"text":" to trigger initPlan caching","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Assuming ","type":"text"},{"text":"uuidv7()","type":"text","marks":[{"type":"code_inline"}]},{"text":" works on all PG versions","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Verify PostgreSQL 18+; fall back to ","type":"text"},{"text":"gen_random_uuid()","type":"text","marks":[{"type":"code_inline"}]},{"text":" on older versions","type":"text"}]}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Naming Conventions","type":"text"}]},{"type":"paragraph","content":[{"text":"Prisma models use camelCase in TypeScript and must map to snake_case in PostgreSQL:","type":"text"}]},{"type":"table","attrs":{"layout":null},"content":[{"type":"tr","content":[{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Layer","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Convention","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Enforced By","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"TypeScript","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"camelCase","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Prisma model field names","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"PostgreSQL","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"snake_case","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"@map","type":"text","marks":[{"type":"code_inline"}]},{"text":" / ","type":"text"},{"text":"@@map","type":"text","marks":[{"type":"code_inline"}]}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Enums","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"UPPER_SNAKE","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"CHECK constraints","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Indices","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"snake_case","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"idx_table_column","type":"text","marks":[{"type":"code_inline"}]}]}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Deployment Pipeline","type":"text"}]},{"type":"paragraph","content":[{"text":"Migrations follow a strict pipeline order:","type":"text"}]},{"type":"ordered_list","attrs":{"order":1,"listStyle":"number"},"content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"prisma migrate dev --create-only","type":"text","marks":[{"type":"code_inline"}]},{"text":" -- generate and review SQL locally","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"prisma validate","type":"text","marks":[{"type":"code_inline"}]},{"text":" -- verify schema consistency","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Apply to staging/preview database and run integration tests","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"prisma migrate deploy","type":"text","marks":[{"type":"code_inline"}]},{"text":" -- apply in CI/CD pipeline to production","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Monitor for lock contention and query plan regressions","type":"text"}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Relationship to Other Skills","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"database-security","type":"text","marks":[{"type":"code_inline"},{"type":"strong"}]},{"text":": Covers full RLS auditing, PGAudit configuration, Supabase-specific patterns, Convex auth guards, and compliance validation. Use ","type":"text"},{"text":"database-security","type":"text","marks":[{"type":"code_inline"}]},{"text":" for in-depth policy review and access simulation. Use ","type":"text"},{"text":"db-enforcer","type":"text","marks":[{"type":"code_inline"}]},{"text":" for schema design and migration patterns that include RLS as part of the integrity workflow.","type":"text"}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Delegation","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Audit existing schema for missing constraints or indices","type":"text","marks":[{"type":"strong"}]},{"text":": Use ","type":"text"},{"text":"Explore","type":"text","marks":[{"type":"code_inline"}]},{"text":" agent","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Plan a zero-downtime migration strategy for production databases","type":"text","marks":[{"type":"strong"}]},{"text":": Use ","type":"text"},{"text":"Plan","type":"text","marks":[{"type":"code_inline"}]},{"text":" agent","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Execute a full schema refactor with type alignment and RLS setup","type":"text","marks":[{"type":"strong"}]},{"text":": Use ","type":"text"},{"text":"Task","type":"text","marks":[{"type":"code_inline"}]},{"text":" agent","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Review RLS policies for bypasses and performance issues","type":"text","marks":[{"type":"strong"}]},{"text":": Use ","type":"text"},{"text":"database-security","type":"text","marks":[{"type":"code_inline"}]},{"text":" skill","type":"text"}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"References","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"PostgreSQL integrity patterns, UUIDv7, virtual columns, and temporal constraints","type":"text","marks":[{"type":"link","attrs":{"href":"references/postgres-integrity.md","title":null}}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Prisma architecture, TypedSQL, extensions, and edge-first patterns","type":"text","marks":[{"type":"link","attrs":{"href":"references/prisma-architecture.md","title":null}}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Migration safety protocols, destructive changes, and rollback strategies","type":"text","marks":[{"type":"link","attrs":{"href":"references/migration-safety.md","title":null}}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Row-Level Security, column-level security, and audit logging","type":"text","marks":[{"type":"link","attrs":{"href":"references/rls-security.md","title":null}}]}]}]}]},{"type":"hr","attrs":{"markup":"---"}}]},"metadata":{"date":"2026-06-05","name":"db-enforcer","author":"@skillopedia","source":{"stars":12,"repo_name":"agent-skills","origin_url":"https://github.com/oakoss/agent-skills/blob/HEAD/skills/db-enforcer/SKILL.md","repo_owner":"oakoss","body_sha256":"6c220596985072408fdadb3f745de97bd5e57beba3459c2bf01674349a37f046","cluster_key":"ae0652d1d39528e87bc2d7226717c78c17fff84cecc5c981a113676f259ebce9","clean_bundle":{"format":"clean-skill-bundle-v1","source":"oakoss/agent-skills/skills/db-enforcer/SKILL.md","attachments":[{"id":"3b82da0c-763b-5b7f-94fe-27d8b67b18db","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/3b82da0c-763b-5b7f-94fe-27d8b67b18db/attachment.md","path":"references/migration-safety.md","size":8698,"sha256":"41c61d997d13997610b2c9d530e001e42b7da8164f136fe327ffc161871ed0bd","contentType":"text/markdown; charset=utf-8"},{"id":"070936f0-73be-57a4-9d69-8a20a9cfb7d1","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/070936f0-73be-57a4-9d69-8a20a9cfb7d1/attachment.md","path":"references/postgres-integrity.md","size":3154,"sha256":"164a5443907e068ab1e349a89e62af8cc6f0aebb90f3712d1b4180f83177894d","contentType":"text/markdown; charset=utf-8"},{"id":"2a839c50-00d4-58ee-aca3-caf96c1dd2e6","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/2a839c50-00d4-58ee-aca3-caf96c1dd2e6/attachment.md","path":"references/prisma-architecture.md","size":3098,"sha256":"6e5914531bd79bf614e87c7a924ce1c68d5717b24e87bace875d27f32fd35332","contentType":"text/markdown; charset=utf-8"},{"id":"8456bd0c-c26b-5c57-8da1-24ce7ea55389","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/8456bd0c-c26b-5c57-8da1-24ce7ea55389/attachment.md","path":"references/rls-security.md","size":2739,"sha256":"743327614a7b34b790efaa3324c7453684e1fb98b1d68172398b05278711807d","contentType":"text/markdown; charset=utf-8"}],"bundle_sha256":"5bc126375181762b1f57959c9e26c18c649a484345ef471a846157c481bcd656","attachment_count":4,"text_attachments":4,"attachment_storage":"skillopedia-attachments-v1","binary_attachments":0,"excluded_attachments":[]},"cluster_size":1,"skill_md_path":"skills/db-enforcer/SKILL.md","import_metadata":{"date":"2026-06-05","author":"@skillopedia","version":"v1","category":"security","category_label":"Security"},"exact_dupes_collapsed_into_this":0},"license":"MIT","version":"v1","category":"security","metadata":{"author":"oakoss","version":"1.1"},"import_tag":"clean-skills-v1","description":"Enforces database integrity for PostgreSQL and Prisma systems. Use when designing schemas, writing migrations, or configuring Row-Level Security. Use for type-safe SQL, naming alignment, constraint validation, zero-trust RLS policies, UUIDv7 primary keys, and zero-downtime deployments."}},"renderedAt":1782980342499}

DB Enforcer Overview Enforces data integrity and architectural consistency between the TypeScript application layer and the PostgreSQL persistence layer. Prevents type drift by ensuring CHECK constraints mirror TypeScript types, migrations are generated before applying changes, and Row-Level Security protects every table. When to use: Schema design, migration planning, RLS policy authoring, Prisma model mapping, constraint auditing, zero-downtime deployments. When NOT to use: Application-level business logic, frontend state management, non-PostgreSQL databases. For full RLS auditing, performa…