Skill

Owner: Forge · Team: Dev Team · Source: ~/.openclaw/dev-shared/skills/db-migration/SKILL.md

Write a silver-castle SQL migration — idempotency law (they re-run every boot), numbering by directory listing, new-table boilerplate, RLS/permissions in-file.


Playbook (mirrored from disk)

DB Migration

The law

Migrations in apps/api/db/migrations/NNN_description.sql re-run on EVERY api container boot —
there is NO applied-ledger. Every statement must be idempotent or every future deploy breaks:

  • create table if not exists · create index if not exists · insert ... on conflict do nothing
  • Policies wrapped:
do $$ begin
  if not exists (select 1 from pg_policies where policyname = 'p_name' and tablename = 't') then
    create policy p_name on t ...;
  end if;
end $$;

Numbering

ls apps/api/db/migrations/ | sort → take max(NNN)+1. NEVER number from memory — a duplicate
036 already exists as a monument to that mistake.

File shape

Header comment: -- NNN — description + intent + end-state + the word Idempotent.
New tables: id uuid primary key default gen_random_uuid(), FKs
references sc_profiles(id) on delete cascade, created_at timestamptz not null default now(),
enable row level security, named indexes sc_<table>_<col>_idx / _uniq.
Permission/RLS seed rows ship in the SAME file as the feature’s table.

Boundaries

sc_* tables only — the Supabase is shared with SmartBudget + admin. Never touch auth./storage.
schemas. RLS via the sc_can() helper for browser-direct paths.

Verification: re-read the file asking “what happens on the SECOND run?” — then api typecheck+test.