Migration Lifecycle
Schema changes should move through a repeatable path from local edit to committed migration to deployed environment.
The recommended lifecycle is:
- edit Inspire
- generate a migration
- review the plan
- apply locally
- deploy
This path is deliberately linear. Each step produces a reviewable artifact, and nothing is applied to a database without a committed migration in version control.
Why This Lifecycle Exists
Schema changes are one of the few places where product speed and operational risk collide directly. A team wants to move quickly, but production data needs a reviewable history.
Kizaki handles that tension by separating:
- the schema you want (your
.inspirefiles) - the migration plan that gets you there (the generated
plan.migration) - the environments that need to apply that plan (local, staging, production)
Migration files are part of your app source, not hidden state. They are committed to version control alongside the schema, and they are the portable unit that moves between environments.
What A Migration Directory Contains
When you run kizaki migrate plan --name add-project-status, Kizaki creates a directory under migrations/:
migrations/
0001_initial/
plan.migration
snapshot.json
0002_add_project_status/
plan.migration
snapshot.jsonEach migration directory contains two files:
plan.migration— the SQL operations expressed in Inspire syntax. This is the diff: the specific changes needed to move the database from the previous state to the new one. It might containadd_column,create_table,drop_index, or other structural operations.snapshot.json— the full schema state after this migration is applied. This is the checkpoint: a complete picture of what the database looks like at this point in the migration history.
You review the plan before deploying. The compiler uses the snapshot to calculate the next diff.
What Changes Need A Migration
Not every schema change requires a migration. The distinction is between structural changes to the database and configuration changes to the runtime.
Requires a migration:
- adding or removing an entity
- adding, removing, or renaming a field
- changing a field's type
- adding or removing an index
- changing a field from optional to required (or vice versa)
- any change to the physical database schema
Does not require a migration (hot-reloads):
- adding, removing, or changing
@grantand@denypolicies - adding or changing route declarations
- adding or changing schedule declarations
- updating effect configuration
- changing auth provider settings
The second category hot-reloads because those changes only affect artifact.json, which the database engine can reload without altering the database schema. Policy changes take effect immediately during local development and on the next deploy in hosted environments.
Fail-Closed Behavior
The dev server refuses to start if your source schema has diverged from committed migrations. You cannot accidentally develop against a database shape that does not match your migration history.
If you edit your .inspire files and the resulting schema differs from the most recent migration snapshot, kizaki dev stops with a message telling you the schema has diverged. The fix is straightforward:
kizaki migrate plan --name describe-the-changeThis generates a new migration that bridges the gap. Once the migration exists, the dev server starts normally.
This behavior is intentional. Local drift (where a developer's database has a shape that no migration describes) is one of the most common sources of deployment failures. By refusing to start, the dev server ensures that the migration history is always the authoritative record of the database shape.
RESOLVE Markers
Sometimes the compiler cannot infer your intent from the schema diff alone. The most common case is a field rename: did you rename title to name, or did you drop title and add a new name field?
When the compiler encounters this ambiguity, it inserts a [RESOLVE] marker in the generated plan.migration:
[RESOLVE] Field "title" was removed and "name" was added on entity Project.
Option A: rename_column title -> name
Option B: drop_column title, add_column nameYou edit the plan to keep the correct option and remove the marker. The migration will not apply until all [RESOLVE] markers are resolved.
Destructive changes (dropping a column that contains data, dropping an entire entity) are also flagged. These changes are allowed, but they require explicit confirmation. Locally, the migration applies with a warning. In hosted environments, kizaki deploy requires the --allow-destructive flag as a deliberate acknowledgment.
Migrations Across Environments
Committed migrations are the portable unit that moves between environments. The same migration history applies everywhere:
- Locally:
kizaki migrate applyruns pending migrations against your embedded Postgres. - Staging:
kizaki deploy --env staginguploads the app and applies pending migrations to the staging database. - Production:
kizaki deploy --env productiondoes the same against the production database.
There is no separate migration path for each environment. The migration files in your repository are the single source of truth. Migrations are committed to version control alongside your schema and application code because they are part of the app, not a side channel.
What A Healthy Workflow Looks Like
In practice, healthy schema work means:
- schema edits happen in Inspire, not in raw SQL
- migrations are generated soon after the schema change, while the intent is fresh
plan.migrationis reviewed before committing: it is a diff, and diffs deserve review- RESOLVE markers are addressed explicitly, not guessed at
- local and hosted environments converge through the same migration history
- destructive changes are flagged and acknowledged, not silently applied