# 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) ```json { "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 (5–20 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 file** — `db/seed.sql`. Not split per entity (ordering matters). Smoke-fixture shape: ```sql -- Generated by /schema-design Phase 5 — -- 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 5–20 rows per non-junction entity, with FK references wrapping modulo the parent count. For "Test fixtures" — group by test name via SQL comments: ```sql -- 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`: ```bash # 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 < 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).