Use BEFORE editing any file in `supabase/migrations/` or `supabase/schemas/`, OR when the user runs a `/database` subcommand (`compact local migration`, `rls scenarios`, `align`). Encodes the three contracts that protect the Grida database layer: applied migrations are immutable, RLS implementation mirrors tests (never the reverse), `schemas/*.sql` is the human-readable end-state. Companion to `supabase/AGENTS.md` (RLS, grants, security boundaries).
74
92%
Does it follow best practices?
Run evals on this skill
Adds up to 20 points to the overall score
View guide
Passed
No findings from the security scan
Three contracts this skill protects:
schemas/*.sql is the human-friendly description of the final shape.supabase/AGENTS.md is the harder rule layer (RLS, grants, security
boundaries). This skill covers the recurring workflow tasks. Read both.
For realistic optional Library data without changing schema history or the canonical base seed, use the opt-library skill. Return here when that work changes migrations, schemas, RLS, or grants.
When asked to "merge migrations" or "consolidate", the temptation is to
collapse every supabase/migrations/* into a single canonical file.
This is wrong if any of those files have already been applied to a
deployed environment. Rewriting an applied migration:
supabase_migrations.schema_migrations
records as already-run.db reset produce a different starting state for new
contributors than what the existing environment holds.Before merging anything, classify each migration:
| Class | Signal | Allowed action |
|---|---|---|
| Applied (production) | User confirms it's in production, OR file has been on main long enough to have shipped. | Read-only. Never edit, never delete. |
| Applied (committed peers) | Tracked on the current branch but originated upstream (already on main / canary). | Read-only. Never edit, never delete. |
| Local-only (this PR) | Newly added on the current working tree / branch, not yet merged to a deployed branch. | Free to merge, rename, delete, rewrite. |
None of these signals replace user confirmation. The only ground truth is the deployed
schema_migrationstable on staging/prod, which the agent cannot read.git logandgit statusindicate likelihood, not certainty. Default to asking.
Old timestamps don't mean "applied" — they can be brand-new files added to fix an ordering bug.
/database compact local migrationMerge multiple local-only migration files into one (or a few) before
the PR ships. Local development naturally accumulates many small
migrations for fast iteration without db reset; production prefers
one coherent migration per feature.
ADD COLUMN
then DROP COLUMN — both vanish in the merged file).ADD … then DROP churn, no superseded function defs.
Idempotent forms (CREATE TABLE IF NOT EXISTS, CREATE OR REPLACE FUNCTION, ADD COLUMN IF NOT EXISTS) make local re-runs safe.supabase db reset locally. Run supabase db test if pgTAP
covers the affected tables.Local-only (mergeable):
20260508120000_grida_billing_account_provisioning_uid.sql
20260508130000_grida_billing_metronome.sql
20260509120000_grida_billing_debit_cache.sql
20260509130000_grida_billing_alerts_multi_tier.sqlApplied (untouchable):
20260506132900_grida_billing.sql
20260507000000_grida_billing_backfill_provision.sql
20260507223000_grida_billing_security_invoker.sqlRight move: write one consolidated 20260508130000_grida_billing_metronome.sql
(latest timestamp; v2 projector from alerts_multi_tier replaces v1
from metronome.sql), delete the other three local files, leave the
applied trio untouched. Wrong move: cat all seven into one.
/database rls scenariosWrite or review RLS test scenarios for a tenant-scoped surface. Output is pgTAP coverage proving who can read/write what across personas. Not a description of the current implementation.
/database rls scenarios <surface>.Implementation mirrors the test, not the other way around.
In RLS, the user journey is the spec. If a test says "a member of
org A cannot read org B's project rows", that is a fact about how
the product must behave. The implementation's job is to satisfy that
fact. If the implementation currently leaks org B's rows, that's a
security bug — fix the implementation, do not weaken the test.
Resist any pressure (including from yourself, mid-implementation):
SET LOCAL ROLE service_role to make a test pass.is(count, 0) with ok(count >= 0).A test failing because the policy is wrong is the test doing its job. A test failing because the test is wrong (mis-seeded fixture, typo'd UUID) gets fixed mechanically — never relax the assertion.
You are the database/security expert helping the user lock down the spec:
auth.uid() returns NULL. A policy reading
auth.uid() = owner_id becomes NULL = … (always false-ish);
WITH CHECK must fail closed independently.RETURNING clause leaks — an INSERT … RETURNING * or
UPDATE … RETURNING * may emit columns from rows a peer SELECT
policy hides. Test that the writer doesn't leak fields they
can't read back via SELECT.archived_at-set rows — visibility differs.authenticated when
organization_id ∈ user's owned orgs". Same fact, SQL-shaped.
If the user's words and the SQL diverge, stop and ask.supabase/seed.sql), not ad-hoc UUIDs.One pgTAP file per surface (or per logical persona group when large).
Skeleton + fixture/session conventions live in supabase/AGENTS.md
§ RLS testing — point readers there rather than re-list.
service_role to read tenant rows — bypasses RLS,
proves nothing./database alignBring supabase/schemas/*.sql back in sync with the migrated state.
Schemas are the human-friendly source of truth for the final shape
of each domain schema. Migrations are the executable history; schemas
are the readable end-state.
grida_* schema.schemas/*.sql and migrations/* visibly disagree./database align.schemas/*.sql is for (and isn't)| Concern | schemas/*.sql | migrations/*.sql |
|---|---|---|
| What runs on the DB | No | Yes — supabase applies these. |
| Source of truth for execution | No | Yes. |
| Source of truth for humans | Yes — read first. | No — chronological, hard to reason about. |
| Updated | Manually, periodically. | Via supabase migration new. |
| Drift | Best-effort, may lag. | Never — runs against real DBs. |
align is the periodic reset that keeps the human-readable layer
trustworthy. See supabase/AGENTS.md for the upstream policy.
grida_billing). Don't align
everything in one pass — too easy to miss a divergence.pg_dump would give you the truth too,
but in the wrong shape (alphabetised, comments stripped, catalog
noise) and is harder to diff against a hand-organised schema file
than just reading the migrations.schemas/<name>.sql. Common deltas:
CREATE OR REPLACE, not updated.COMMENT ON COLUMN; schemas often
forget to mirror.schemas/<name>.sql to the migrated end-state. Keep
the file's existing organisation (sections by table, header
comments). Group grants and policies under the table they belong
to — not by chronology.compact flow above
for unshipped local-only ones).supabase db reset afterwards as a smoke check.schemas/*.sql instead of a migration to "fix a column" —
the schema file is reference, not executable. The DB won't see it.2e0d276
If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.