# Migrate New

> Create a new database migration via the Ent + Atlas workflow

- Skill: `kalbasit/migrate-new` (Agent Skill)
- Install (CLI): `npx skillmds@latest add kalbasit/migrate-new`
- Raw SKILL.md: https://api.skillmd.com/api/skills/kalbasit/migrate-new/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: AI & ML
- Author: kalbasit (https://skillmd.com/u/kalbasit)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/kalbasit/migrate-new

---


# Create a New Migration

Migrations in ncps are derived from the Ent schemas under `ent/schema/`.
The schema is the only source of truth for DDL; SQL migration files are
generated by `cmd/generate-migrations` (Atlas, used as a Go library) and
applied at runtime by `ncps migrate up` (goose-driven).

Never hand-author a `.sql` file under `migrations/<dialect>/` — Atlas
seals the directory with `atlas.sum` and CI rejects any drift. The only
exception is the `--sql-only` mode below, which is used for data
backfills and the four-step NOT NULL recipe.

## Workflow

1. **Edit the Ent schema.** Open `ent/schema/<entity>.go` and add or
   modify the field, edge, index, or annotation. New entities go in a
   new file under `ent/schema/`.

   The five codegen invariants apply — see `.agent/skills/ent-schema/SKILL.md`
   and `cmd/ent-lint/` for enforcement details.

1. **Regenerate the Ent client.** This refreshes `ent/migrate/schema.go`,
   which `cmd/generate-migrations` then diffs against the on-disk
   migration history.

   ```bash
   go generate ./ent/...
   # or
   task ent:generate
   ```

   Commit the regenerated `ent/` tree alongside the schema change — the
   `ent-codegen-drift-check` derivation in `nix flake check` rejects any
   commit where the tree is stale.

1. **Generate per-dialect Atlas migrations.** One descriptive snake_case
   name produces three new `.sql` files (sqlite + postgres + mysql) under
   `migrations/<dialect>/` sharing one timestamp prefix, plus regenerates
   `atlas.sum` for each dialect.

   ```bash
   go run ./cmd/generate-migrations --name=add_users_table
   # or
   task migrations:gen NAME=add_users_table
   ```

   Placeholder names (`auto`, `wip`, `tmp`, `todo`, `temp`, `test`,
   empty, whitespace) are rejected — supply a descriptive identifier.

1. **Review the generated SQL.** Open each new file under
   `migrations/<dialect>/` and verify:

   - The header carries `-- +goose Up` and `-- +goose Down` markers.
   - SQLite files that use `PRAGMA foreign_keys = OFF` to drive a
     temp-table-copy-rename rewrite ALSO carry
     `-- +goose NO TRANSACTION` at the top (goose otherwise wraps the
     migration in a transaction, which is incompatible with the PRAGMA).
   - The down migration is a clean inverse of the up — Ent's
     auto-generated down handles most cases, but verify nontrivial
     migrations by hand.

1. **Data backfills + four-step NOT NULL recipe.** Use `--sql-only` to
   emit empty Goose stubs (no Ent diff is run); fill them in manually.

   ```bash
   go run ./cmd/generate-migrations --sql-only --name=backfill_emails
   # or
   task migrations:sql NAME=backfill_emails
   ```

   The expand-contract policy applies — see `CLAUDE.md` and the
   data-model spec for the rationale. Never alter a column in place;
   split the change across multiple migrations.

## Skips and overrides

- `--skip=sqlite,mysql` skips dialects you don't want regenerated. This
  is the escape hatch for hand-translated migrations (e.g. the Postgres
  bridge in §8 of the migrate-to-ent-and-atlas change).
- `--postgres-url=<dsn>` / `--mysql-url=<dsn>` (or
  `NCPS_GEN_POSTGRES_URL` / `NCPS_GEN_MYSQL_URL`) override the dev
  database URLs. Default URLs target the localhost `test-user@test-db`
  that `nix run .#deps` provides.

## Verifying

- `task ent:check` (or `go generate ./ent/... && git diff --exit-code ./ent/`)
  confirms the Ent client is up to date.
- `go run ./cmd/ent-lint --root .` checks schemas against the
  implemented invariants (A1, A2, A4); A3 and A5 are enforced by code
  review until wired into the linter.
- `go run ./cmd/atlas-sum-check --root .` confirms every `atlas.sum`
  matches the directory contents.
- `go test -race -run TestSchemaEquivalence ./migrations/...` runs the
  load-bearing golden test against all three dialects (uses
  `nix run .#deps` for the Postgres + MySQL paths).

All four checks are wired into `nix flake check` as separate derivations
under `nix/checks/flake-module.nix`.

