YYYYMMDDHHMMSS_name.sql: a 14-digit UTC prefix and lowercase descriptive name. Read ohmyhost init --dry-run --json for the exact source contract.
Migrations are expand-only. Admitted statements create tables, types, domains, sequences, views/materialized views, triggers, indexes or SQL/PLpgSQL functions (SECURITY DEFINER requires SET search_path); ALTER TABLE … ADD, COMMENT ON, CREATE SCHEMA IF NOT EXISTS auth|extensions|private, CREATE EXTENSION IF NOT EXISTS pgcrypto|btree_gist|unaccent, and SET check_function_bodies=false are the other allowed forms. DROP/TRUNCATE, row changes, CREATE OR REPLACE, other ALTER, GRANT, DO and transaction control are refused. Text including comments cannot contain ohmyhost, supabase, policies/RLS, storage references, auth references other than admitted quoted Better Auth tables, or Supabase anon/authenticated/service_role authority. Init reports migration_sql_not_admitted with the refused file/reason.
Existing tables gain only nullable columns without defaults and non-unique indexes. Constraints, foreign keys, triggers and unique indexes may attach only to tables created within the same pending migration run. All pending files apply atomically with a thirty-second statement and five-second lock timeout; CREATE INDEX CONCURRENTLY therefore fails. Applied files are immutable: never edit, rename or remove one; every new timestamp follows the applied catalog. Maximum 128 files,256 KiB each,2 MiB total, UTF-8/LF without BOM.
Source planning checks canonical filenames, not complete SQL admission. Run init before paying for a build; execution/catalog validation may still fail as database_migration_failed, with none of the new files applied. Read DATABASE_MIGRATION_FAILED’s file and reason: MIGRATION_CHANGED_EXISTING_SCHEMA, MIGRATION_EXECUTION_FAILED, APPLIED_MIGRATION_CHANGED or MIGRATION_SQL_NOT_ADMITTED. Fix the cause and plan the new commit; never reset to repair a failed migration.