Kizaki
Concepts

Migration Lifecycle

Schema changes should move through a repeatable path from local edit to committed migration to deployed environment.

The recommended lifecycle is:

  1. edit Inspire
  2. generate a migration
  3. review the plan
  4. apply locally
  5. 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 .inspire files)
  • 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.json

Each 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 contain add_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 @grant and @deny policies
  • 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-change

This 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 name

You 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 apply runs pending migrations against your embedded Postgres.
  • Staging: kizaki deploy --env staging uploads the app and applies pending migrations to the staging database.
  • Production: kizaki deploy --env production does 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.migration is 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

On this page