KeiSeiKit-1.0/skills/schema-design/phase-4-migrations.md
Parfii-bot a4e667de10 KeiSeiKit-public — clean state
Single-commit clean baseline after security scrub of niche-tells,
project codenames, internal jargon, and contributor-email leaks.

Contents:
- 100 Rust crates (_primitives/_rust/)
- 37 agent manifests (_manifests/) + generated specs (_generated/)
- 67 user-invocable skills (skills/)
- 33 hooks (hooks/)
- Composition blocks (_blocks/)
- Documentation (docs/, README.md)
- TS adapter packages (_ts_packages/)
- Assembler (_assembler/)
- Roles (_roles/)
- Templates (_templates/)
- Forgejo CI (.forgejo/)

Author: Denis Parfionovich <info@greendragon.info>

License: see LICENSE.
2026-05-01 12:09:03 +08:00

4.3 KiB

Phase 4 — Migration scaffold + first migration + kei-migrate wiring

Package db/schema.sql (from Phase 3) into a proper timestamp-prefixed migration pair under migrations/, and emit the kei-migrate invocation the user should run.

4a — Create migrations/ directory (no AskUserQuestion)

If migrations/ does not yet exist in the repo, create it. Emit one .keep file or rely on the first migration to anchor it.

If ORM = Prisma: the directory is prisma/migrations/ and the runner is prisma migrate dev — skill notes this and skips kei-migrate wiring (reference the handoff but DO NOT overwrite Prisma's own layout).

If ORM = SQLAlchemy: the directory is alembic/versions/ and the runner is alembic upgrade head — same rule, skip kei-migrate wiring.

For every other ORM value (none / Drizzle / SQLx): use migrations/ with kei-migrate.

4b — Generate timestamp + filename

  • Timestamp format: YYYYMMDDHHMMSS (matches kei-migrate create's convention — see _primitives/_rust/kei-migrate/src/cmd_create.rs).
  • Migration name: init_schema.
  • Files:
    • migrations/<ts>_init_schema.sql (up — full DDL from Phase 3)
    • migrations/<ts>_init_schema.down.sql (down — DROP TABLE reverse order)

4c — Up migration content

Copy db/schema.sql contents into the up file verbatim, with a one-line header:

-- kei-migrate: init_schema  (generated <YYYY-MM-DD>)
-- See db/schema.sql for the schema SSoT.

Do not split one migration per table — the initial schema ships as ONE migration by convention. Subsequent changes each get their own timestamp.

4d — Down migration content

Emit DROP TABLE IF EXISTS <name> CASCADE; for every entity, in REVERSE dependency order (children before parents — junctions first, then leaf entities, then referenced entities last).

If any table is flagged -- IRREVERSIBLE by the user (e.g. contains critical data once populated), replace the DROP TABLE line with:

-- IRREVERSIBLE: this table holds production data; manual restore required.
-- Abort reverse migration.
SELECT RAISE(FAIL, 'irreversible: init_schema') ;  -- or equivalent per DB

See db-migration-hygiene.md for the irreversible pattern.

4e — Wire kei-migrate (AskUserQuestion)

{
  "questions": [
    {
      "question": "Add kei-migrate to the project?",
      "header": "Runner",
      "multiSelect": false,
      "options": [
        {"label": "Add to Cargo workspace as path dep", "description": "Rust projects — edit root Cargo.toml members. Skill will NOT edit; emits the snippet for you to paste."},
        {"label": "Install prebuilt binary (system-wide)", "description": "Any stack — `cargo install --path _primitives/_rust/kei-migrate` once; repo stays tool-agnostic"},
        {"label": "Use existing runner (Prisma / Alembic / Drizzle-kit / goose / Atlas)", "description": "Skill skips kei-migrate; records the handoff in the report"},
        {"label": "Decide later", "description": "Files land on disk; runner wiring deferred"}
      ]
    }
  ]
}

Store the answer as RUNNER.

4f — Emit the next-step command (inline, no AskUserQuestion)

Print a fenced code block tailored to DB + RUNNER:

# Load DB URL from SSoT (RULE 0.8)
set -a && source secrets/db.env && set +a

# Preview pending migrations
kei-migrate --database-url "$DATABASE_URL" --dir migrations status

# Apply
kei-migrate --database-url "$DATABASE_URL" --dir migrations up

# Revert the latest (dev only!)
kei-migrate --database-url "$DATABASE_URL" --dir migrations down 1

Reminder (once): secrets/db.env must be chmod 600 and listed in .gitignore BEFORE the first write. Template entry:

# secrets/db.env — chmod 600 before first write
DATABASE_URL=

No values. RULE 0.8 secrets SSoT.

Verify-criterion

  • migrations/<ts>_init_schema.sql exists and equals db/schema.sql body with the one-line header prepended.
  • migrations/<ts>_init_schema.down.sql exists with DROP statements in reverse dependency order.
  • Filenames use the kei-migrate create timestamp convention.
  • If ORM ∈ {Prisma, SQLAlchemy} — kei-migrate files are NOT created; instead record a one-line handoff in state: "use <native runner> — schema.sql is the design SSoT, port it to the native format."
  • Reminder about secrets/db.env emitted exactly once.