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 plancompares 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
.inspirefiles - the committed migration directory under
migrations/<NNNN_slug>/ plan.inspire, which describes the transitionsnapshot.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.inspiresays what changedsnapshot.jsongives 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.jsonThere 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:
- edit Inspire
- iterate locally until the shape is right
- run
kizaki migrate plan - review
plan.inspire - run
kizaki migrate apply - 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.jsonThe 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 statuskizaki 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.inspirekizaki 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: noneReview 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.inspirebefore 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