Kizaki
ReferenceInspire

Migrations

Evolve Inspire schemas through committed migration history, reviewed plans, and a dev loop that stays aligned with that history.

Migrations are the reviewed history of how your database catches up to the schema you declared in Inspire.

The schema is still the source of truth. You do not hand-author SQL migrations. You change Inspire, generate a migration plan, review that plan, and commit the result as part of the repo. That is the important model to keep in your head.

What A Migration Is In Kizaki

A migration is not “some SQL file the ORM happened to emit.” It is a repo-tracked transition between two schema states.

In practice, that means:

  • Inspire declares the target schema
  • kizaki migrate plan compares the current schema to the latest committed snapshot
  • Kizaki writes a migration directory under migrations/<NNNN_slug>/
  • you review the generated plan before applying and committing it

The goal is to keep schema evolution visible and reviewable without forcing you to manually think in DDL.

The Important Artifacts

The main artifacts are:

  • your .inspire files
  • the committed migration directory under migrations/<NNNN_slug>/
  • plan.inspire, which describes the transition
  • snapshot.json, which captures the compiled schema state the migration points to

You should think about them like this:

  • Inspire says where the schema should end up
  • plan.inspire says what changed
  • snapshot.json gives the migration system a stable historical comparison point

Directory Shape

The current migration authority in the repo lives under migrations/, not under hidden local-only state.

migrations/
  0001_initial/
    plan.inspire
    snapshot.json
  0002_add_due_date/
    plan.inspire
    snapshot.json

There may also be generated SQL output during local workflows, but the important committed artifacts are the plan and the snapshot.

Example plan.inspire

migration "0002_add_due_date" {
  add Task.dueDate: datetime?
  add Task.status: TaskStatus = Todo
  rename Task.ownerId -> Task.assigneeId
}

You should be able to read a plan file and understand the intended schema transition without mentally translating raw SQL.

The Development Logic

This is the part that matters most in day-to-day use.

kizaki dev Is Fast, But Not Authoritative

kizaki dev keeps local development moving, but committed migrations are still the authority.

In the current workflow, the dev stack:

  • compares your current source schema to the latest committed migration snapshot
  • auto-applies committed pending migrations to the local database
  • fails closed if your source schema has moved ahead in a schema-affecting way and you have not generated the next migration

In other words, local development stays fast, but committed migration history remains the thing that defines how a project moves forward.

What That Looks Like In Practice

$ kizaki dev

Starting local stack...
Applying committed migrations...
  0001_initial
  0002_add_due_date

Source schema is ahead of committed migration history.
Run `kizaki migrate plan` before continuing.

That behavior is deliberate. Local development should be fast, but it should not silently invent private schema history that only exists on one machine.

Generate A Migration When The Schema Change Stabilizes

During normal work, the flow is:

  1. edit Inspire
  2. iterate locally until the shape is right
  3. run kizaki migrate plan
  4. review plan.inspire
  5. run kizaki migrate apply
  6. commit the migration directory with the schema change

This keeps the “messy exploration” phase separate from the “reviewable history” phase. You do not need to generate a new migration for every small thought while designing a table, but once the schema change is real, the migration should be generated promptly.

$ kizaki migrate plan --name add_due_date

Generated migrations/0002_add_due_date/plan.inspire
Generated migrations/0002_add_due_date/snapshot.json

The Latest Unapplied Migration Is Treated As A Draft Tip

One subtle but important current behavior is that kizaki migrate plan does not blindly stack endless pending migrations in front of you.

If the latest committed migration exists but has not yet been applied locally, plan treats that latest unapplied committed migration as a draft tip and rewrites it instead of creating another numbered migration on top. If there are multiple pending committed migrations, planning fails instead of guessing.

That matters because it keeps the local migration workflow sane while you are still shaping the current schema change. You do not accumulate accidental migration spam just because you generated a migration slightly early and then kept iterating.

$ kizaki migrate plan

Latest committed migration is still unapplied locally.
Rewriting migrations/0003_rework_tasks/ instead of creating 0004.

The Core Commands

The migration surface is small on purpose:

kizaki migrate plan
kizaki migrate apply
kizaki migrate dry-run
kizaki migrate status

kizaki migrate plan

Use plan after a schema-affecting change.

It:

  • diffs the resolved Inspire workspace against the latest committed snapshot.json
  • writes the next migration plan
  • surfaces ambiguous cases through [RESOLVE] markers instead of guessing

This is the command that turns “I changed the schema” into “here is the reviewed database transition.”

$ kizaki migrate plan

Comparing source schema to migrations/0002_add_due_date/snapshot.json...
Generated migrations/0003_split_assignee/plan.inspire

kizaki migrate apply

Use apply to bring the local database forward through committed migration history.

It applies pending committed migrations in order. In the local workflow, it is the point where the plan stops being just review material and becomes actual local schema state.

$ kizaki migrate apply

Applying 0003_split_assignee...
Migration applied successfully.

kizaki migrate dry-run

Use dry-run when you want to inspect the generated SQL and impact without executing it.

This is especially useful before a deploy or when a migration includes destructive or ambiguous-looking changes and you want to verify the shape before touching any environment.

$ kizaki migrate dry-run

Migration: 0003_split_assignee
- rename Task.ownerId -> Task.assigneeId
- add Task.assignedAt: datetime?

No changes applied.

kizaki migrate status

Use status to answer three questions quickly:

  • what is the latest committed migration?
  • what has been applied?
  • is source ahead of committed migration history?

That last one is especially important. Source-vs-committed drift is usually the signal that you changed Inspire and still need to generate the migration that makes that change real.

$ kizaki migrate status

Latest committed: 0003_split_assignee
Latest applied:   0003_split_assignee
Source drift:     none

Review Material: plan.inspire

The most important migration artifact for day-to-day development is plan.inspire.

Treat it as code review material, not as a machine-generated file to ignore.

What to look for:

  • did this really detect a rename, or did it decide to drop and re-add?
  • did an additive change stay additive?
  • is a destructive operation actually intentional?
  • are any [RESOLVE] markers still present?
  • does the plan match the product change you think you made?
migration "0004_rework_profile" {
  [RESOLVE] rename User.fullName -> User.displayName
  add User.avatarUrl: string?
}

If you see [RESOLVE], the migration is not ready yet.

This is the point of the system: the migration should describe your intent clearly enough that you can review it before it touches data.

When A Migration Is Required

You need a migration when the database schema changes, including things like:

  • adding or removing entities
  • adding, renaming, or removing fields
  • changing indexes or constraints
  • changing enums

You usually do not need a migration for runtime-only changes such as:

  • policy-only changes
  • route changes
  • auth configuration that does not alter the stored schema
  • other behavior that affects runtime enforcement rather than the database shape

The practical rule is simple: if Postgres needs to change shape, you need a migration.

Good Migration Hygiene

Healthy migration work usually looks like this:

  • keep one conceptual schema change per migration when practical
  • generate the migration soon after the schema change stabilizes
  • review plan.inspire before applying
  • commit the migration directory with the schema change itself
  • avoid letting source drift far ahead of committed history

Migrations become much harder to reason about when one migration tries to bundle unrelated cleanup, renames, feature work, and experimental churn together.

How To Think About Local Iteration

The most important mindset is:

  • local iteration can be fast
  • committed migration history must still stay clean

You are not choosing between “instant dev” and “safe history.” Kizaki is trying to give you both:

  • the dev loop keeps local schema work moving
  • migration planning produces the history you review and commit

That is why the migration docs should feel more like workflow guidance than SQL theory.

Zero-Downtime Deploys, Briefly

You do not need to understand the internal mechanics to use migrations well, but the platform’s deploy strategy is designed so schema changes and new app code can roll forward together without a crude “take the app down, run everything, bring it back” model.

The short version is:

  • the migration is applied as part of deploy
  • the platform coordinates the schema transition with the new application version
  • additive changes are straightforward
  • changes like renames and drops are handled in a staged way so old and new app code are not blindly forced through the exact same query assumptions at the same instant

The thing you need to do as a developer is still the same: generate and review the migration carefully. The deploy system handles the transition strategy underneath.

Related guide: Migrations

On this page