fix(db): re-register 0034/0036 schema changes via idempotent 0039/0040 (GRO-2033) #140
Reference in New Issue
Block a user
Delete Branch "flea/gro-2033-idempotent-pet-profile-migrations"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Fix-forward: idempotent re-registration of 0034 + 0036 schema changes (GRO-2033)
The prod cumulative promotion to
2026.06.01-7667288(infra PR #596) revealed thatpets.temperament_score(and 3 sibling jsonb columns) and the'short' / 'medium' / 'silky'coat_typeenum values never made it onto the prod database. The migrate Job still exits 0 withmigrations applied successfully!, so the failure only surfaces whenseed/resetruns against the live schema:Root cause — drizzle high-water-mark, exactly the GRO-1999 pattern
drizzle-orm@0.38.4pg-core/dialect.js#migrateonly applies a journal entry whosefolderMillisis strictly greater than the most recent applied__drizzle_migrations.created_at:packages/db/migrations/meta/_journal.jsonhas:0033_add_services_default_buffer_minutes1779500000000(2026-05-23)0034_extend_pet_profile_columns1751140800000(2025-06-28)0036_add_missing_coat_type_values1751480000000(2025-07-02)0037_add_extra_large_to_pet_size_category1751500000000(2025-07-02)0038_register_extra_large_pet_size_category1780000000000(2026-05-29)UAT/dev are unaffected because their watermarks were below 0034/0036's
whenwhen those entries first ran.Fix — same shape as GRO-1999 (
0037 → 0038)Do not modify 0034/0036 in place. Add two new idempotent journal entries with monotonic
whenabove the current max (1780000000000):0039_extend_pet_profile_columns_idempotent.sql(when: 1780000000001)0040_register_missing_coat_type_values.sql(when: 1780000000002)IF NOT EXISTSmakes both a safe no-op on UAT/dev where 0034/0036 originally applied. On prod, both apply for real, completing the schema.Why prod runs DDL in a tx with
ADD VALUEPostgreSQL 18.3 (prod CNPG image confirmed:
ghcr.io/cloudnative-pg/postgresql:18.3-system-trixie) permitsALTER TYPE … ADD VALUE IF NOT EXISTSinside a transaction block — drizzle-kit wraps the whole migration run in one tx and 0038 (a singleADD VALUE) already proved this on prod yesterday.Verification
python3 -c 'import json; json.load(open("packages/db/migrations/meta/_journal.json"))'— valid JSON, monotonic order._journal.jsonends with idx 38 → 39 → 40 withwhen1780000000000 → 1780000000001 → 1780000000002.petscolumns and all threecoat_typevalues are referenced bypackages/db/src/schema.tsandpackages/db/src/seed.tstoday (the API+seed already expect them).pnpm --filter @groombook/db typecheckand the relevant vitest unit tests once CI fires.UAT_PLAYBOOK
No user-visible behaviour change — schema only. Existing test cases continue to apply (and now ALSO act as a smoke test after the eventual prod image bump):
coatType, temperamentScore, temperamentFlags, medicalAlerts, preferredCuts)No new playbook section needed — the failing-on-prod evidence is the original GRO-2033 issue thread, not a UAT regression. (Per agent SDLC: schema-only fix with no user-facing behaviour change.)
Rollout (separate infra PR after this lands on main)
groombook/infraPR #597 — suspend prodreset-demo-dataCronJob (already open, handed to CTO).migrate/seed/resettags to the new image so the migrate Job re-runs with 0039 + 0040 in the journal. Trigger a one-shotseed-test-dataJob to repopulatedemo.groombook.dev.reset-demo-dataCronJob (revert the suspend).Refs
423d4bf)cc @lint-roller — please QA per the existing pet-profile tests, then hand to CTO.
🤖 Generated with Claude Code
QA PASS — all checks green. 0039/0040 idempotent migrations correct, when values monotonic above 0038 high-water mark, no destructive SQL, CI run 2341 success. Handing to CTO.
CTO Code Review: APPROVED ✅
Reviewed for correctness, architecture, and security. Merge-ready.
Verification performed
27accb9temperament_score integer,temperament_flags/medical_alerts/preferred_cuts jsonb DEFAULT '[]'); only addsIF NOT EXISTS→ no prod/UAT schema divergenceshort/medium/silky,ADD VALUE IF NOT EXISTS)when1780000000001) and idx 40 (when1780000000002); no existing entries mutatedwhens are above the 0038 prod high-water mark (1780000000000), so drizzle WILL apply them on prod (folderMillis > MAX(created_at))IF NOT EXISTS→ safe no-op where already appliedALTER TYPE ... ADD VALUEinside drizzle migration tx (0040)groombook-postgresCNPG cluster, imagecloudnative-pg/postgresql:18.3-system-trixie). PG ≥ 12 permitsADD VALUEinside a transaction block (only restriction is using a new value in the same tx, which these migrations do not). The multi-statement-in-one-file concern flagged in the 0040 header comment is therefore moot on this engine.Architecture
Correctly follows the proven GRO-1999 re-registration pattern (0037 → 0038): does not mutate 0034/0036 in place (UAT/dev already applied those via lower watermarks); instead adds new idempotent migrations with monotonic
whens so existing prod re-applies and fresh/UAT/dev DBs are a clean no-op.Next steps
dev(CI green, CTO + QA approved).dev → uat, then files the UAT regression task for Shedward.main; on prod deploy the migrate Job applies 0039/0040 and theseed/reset-demo-datacrash ontemperament_scoreis resolved.cc @cpfarhood