KeiSeiKit-1.0/skills/schema-design/phase-5-seed.md
Parfii-bot c10e169806 feat(skills): /schema-design 5-phase pipeline
Hub-and-spoke skill that converts "I need a database for app X" into a
designed relational schema + first migration + optional seed.

Pipeline (5 phases, 9 AskUserQuestion calls total, pure-click after intake):
- Phase 1 — batched DB/ORM/scale/style/migration-control click
- Phase 2 — entity list + relations matrix (auto-junction tables)
- Phase 3 — generate DDL with indexes, FKs, constraints; review/revise loop
- Phase 4 — scaffold migrations/ + first timestamped migration + kei-migrate wiring
- Phase 5 — optional seed (smoke / rich / test fixtures / skip)

Cross-refs the five db-* blocks + the kei-migrate Rust primitive added in
commits f884891 and df85792 on this branch. Emits ENV-VAR NAMES only for
DATABASE_URL (RULE 0.8 secrets SSoT). Every file ≤ 121 LOC.
2026-04-21 20:46:32 +08:00

3.5 KiB
Raw Blame History

Phase 5 — Optional seed data + test fixtures

Emit db/seed.sql with deterministic, safe-to-re-run seed rows, OR skip this phase entirely. Single AskUserQuestion to decide.

5a — Seed decision (AskUserQuestion)

{
  "questions": [
    {
      "question": "Seed data for dev / tests?",
      "header": "Seed",
      "multiSelect": false,
      "options": [
        {"label": "Smoke fixture (1 row per entity)", "description": "Minimal — proves schema loads and FKs resolve. Recommended default."},
        {"label": "Rich dev seed (520 rows per entity)", "description": "Realistic playground for local dev; deterministic IDs from a fixed seed"},
        {"label": "Test fixtures only (for integration tests)", "description": "Small, labelled datasets keyed by test-case name"},
        {"label": "Skip — no seed data", "description": "Schema-only delivery; tests will use mocks or runtime factories"}
      ]
    }
  ]
}

Store as SEED.

5b — Generate db/seed.sql (inline, no AskUserQuestion)

Rules, regardless of choice (unless Skip):

  • Idempotent — every INSERT uses ON CONFLICT DO NOTHING (PG/MySQL) or INSERT OR IGNORE (SQLite). Re-running seed is safe.
  • Deterministic PKs — use explicit IDs (1, 2, ...) not relying on sequences. For UUIDs, use fixed values from a documented seed (e.g. uuid_generate_v5(...) or hard-coded dev-only UUIDs).
  • No secrets — no real emails (use user1@example.test), no real phone numbers, no real names. RULE 0.8 still applies: nothing in seed should be or look like a production token.
  • Respect FK order — insert parents before children, junctions last.
  • One filedb/seed.sql. Not split per entity (ordering matters).

Smoke-fixture shape:

-- Generated by /schema-design Phase 5 — <YYYY-MM-DD>
-- Smoke fixture: one row per entity, deterministic IDs.

INSERT INTO users (id, email, name) VALUES (1, 'user1@example.test', 'Seed User')
  ON CONFLICT (id) DO NOTHING;
INSERT INTO organizations (id, name) VALUES (1, 'Seed Org')
  ON CONFLICT (id) DO NOTHING;
INSERT INTO organization_users (user_id, organization_id, role)
  VALUES (1, 1, 'owner') ON CONFLICT DO NOTHING;

For "Rich dev seed" — use counted loops in SQL (PG: generate_series; SQLite: recursive CTE; MySQL: WITH RECURSIVE) to produce 520 rows per non-junction entity, with FK references wrapping modulo the parent count.

For "Test fixtures" — group by test name via SQL comments:

-- fixture: test_user_signup
INSERT INTO users (id, email, name) VALUES (101, 'signup@example.test', 'Signup') ...
-- fixture: test_org_invite
INSERT INTO users (id, email, name) VALUES (201, 'invitee@example.test', 'Invitee') ...

5c — Test-First hook (inline)

If SEED ≠ Skip, emit a smoke-test snippet tailored to DB:

# Smoke-test: load schema + seed, assert row counts.
kei-migrate --database-url "$DATABASE_URL" --dir migrations up
psql "$DATABASE_URL" -f db/seed.sql         # or: sqlite3 <file> < db/seed.sql
psql "$DATABASE_URL" -c "SELECT count(*) FROM users;"

Remind the user: this is a smoke test, not a Test-First contract. Real integration tests live in the project's test suite — see _blocks/rule-test-first.md.

Verify-criterion

  • If SEED = Skip: db/seed.sql is NOT created; state records "seed skipped".
  • Otherwise: db/seed.sql exists, is idempotent, respects FK order, and uses @example.test (or equivalent RFC 2606 reserved) emails only.
  • Smoke-test snippet printed inline in chat (once).
  • Final report can now be emitted (see SKILL.md index).