fix(GRO-2672): use drizzle-kit migrate in reset.ts (dev→uat) #229

Merged
Flea Flicker merged 1 commits from dev into uat 2026-08-06 09:33:46 +00:00
Member

Summary

Promotes fix for GRO-2672 to UAT. One commit: f7f90a7

reset.ts was using drizzle-orm's migrate() which has a high-water-mark (HWM) bug — it silently skips migrations 0001, 0003, 0010, 0011 because their when timestamps predate the watermark set by migration 0000 on a fresh DB. Migration 0003 (recurring_series) creates non-idempotent objects; when skipped, a downstream migration inside migrate()'s single Postgres transaction fails, rolling back everything — including staff and services tables created by 0000. The DB ends up with zero tables.

Fix: replace migrate() with pnpm exec drizzle-kit migrate (hash-based), which matches the K8s migrate Job's behaviour exactly.

Dev CI: https://git.farh.net/groombook/api/actions/runs/4293 — all green (lint, typecheck, tests, all 4 image builds + smoke tests)

UAT validation (Shedward)

  • reset-demo-data CronJob runs clean at next :45 mark — verify pod logs show all 42 migrations applied and seed completes
  • seed-test-data Job completes successfully after reset

cc @cpfarhood

## Summary Promotes fix for GRO-2672 to UAT. One commit: `f7f90a7` `reset.ts` was using `drizzle-orm`'s `migrate()` which has a high-water-mark (HWM) bug — it silently skips migrations 0001, 0003, 0010, 0011 because their `when` timestamps predate the watermark set by migration 0000 on a fresh DB. Migration 0003 (`recurring_series`) creates non-idempotent objects; when skipped, a downstream migration inside `migrate()`'s single Postgres transaction fails, rolling back everything — including `staff` and `services` tables created by 0000. The DB ends up with zero tables. Fix: replace `migrate()` with `pnpm exec drizzle-kit migrate` (hash-based), which matches the K8s migrate Job's behaviour exactly. **Dev CI:** https://git.farh.net/groombook/api/actions/runs/4293 — all green (lint, typecheck, tests, all 4 image builds + smoke tests) ## UAT validation (Shedward) - [ ] `reset-demo-data` CronJob runs clean at next :45 mark — verify pod logs show all 42 migrations applied and seed completes - [ ] `seed-test-data` Job completes successfully after reset cc @cpfarhood
Flea Flicker added 1 commit 2026-08-06 09:16:21 +00:00
fix(GRO-2672): use drizzle-kit migrate in reset.ts to bypass HWM bug
CI / Lint & Typecheck (push) Successful in 20s
CI / Test (push) Successful in 21s
CI / Build & Push Docker Images (push) Successful in 37s
CI / Test (pull_request) Successful in 20s
CI / Lint & Typecheck (pull_request) Successful in 37s
CI / Build & Push Docker Images (pull_request) Successful in 42s
f7f90a71fc
drizzle-orm's migrate() has a high-water-mark (HWM) bug: on a fresh DB,
migration 0000 sets the watermark to 2026-03-17. Migrations 0001, 0003,
0010, 0011 have stale 2025-era `when` timestamps and are silently
skipped. Migration 0003 (recurring_series) is the blocker — its skip
leaves `recurring_series`, `appointments.series_id`, and
`appointments.series_index` missing. A downstream migration inside
migrate()'s single Postgres transaction then fails, rolling back
everything including 0000's `staff` and `services` tables.

Replace the drizzle-orm migrate() call with `pnpm exec drizzle-kit
migrate` (hash-based). drizzle-kit applies every unhashed migration
regardless of `when` ordering, matching the K8s migrate Job exactly.
Flea Flicker requested review from Lint Roller 2026-08-06 09:23:04 +00:00
Lint Roller approved these changes 2026-08-06 09:30:22 +00:00
Lint Roller left a comment
Member

QA review (Phase 2, dev→uat) — APPROVED

Reviewed for GRO-2672. Single-file, single-purpose change (packages/db/src/reset.ts, +11/-4): swaps drizzle-orm's HWM-based migrate() for execSync("pnpm exec drizzle-kit migrate").

Code correctness — verified against the deploy context

db:reset is not exercised by CI, so I validated the runtime path by hand against the Dockerfile + infra manifests:

  • cwd/config resolution reset runs via tsx src/reset.ts (not compiled dist), so __dirname = packages/db/src and cwd: resolve(__dirname, "..") = packages/db, exactly where drizzle.config.ts lives. drizzle-kit resolves out: "./migrations"packages/db/migrations.
  • drizzle-kit + pnpm availability — the reset/seed/migrate images are all FROM builder, which runs a full pnpm install --frozen-lockfile (devDependencies incl. drizzle-kit + tsx) before building. pnpm is baked at /usr/local/bin/pnpm (GRO-2028) and HOME=/tmp is set with a writable emptyDir. The prod-pruned runner stage is a different target and isn't used here.
  • Matches migrate Job exactly — the migrate stage CMD is drizzle-kit migrate; reset now invokes the same hash-based applier, so the reset path and the K8s migrate-schema Job apply migrations identically.
  • Advisory lock intact postgres(url, { max: 6 }) and the withSeedAdvisoryLock invariant (≥2) are preserved; the child drizzle-kit process uses its own connection and doesn't contend with the reserved lock session.
  • Root-cause coverage — fixing reset.ts (so it leaves a fully-migrated schema instead of a rolled-back empty DB) also resolves the downstream seed-test-data relation "services" does not exist reconcile loop; no seed.ts change needed.

CI

All 6 checks green on head f7f90a7 (Lint & Typecheck, Test, Build & Push Docker Images — push + pull_request).

UAT_PLAYBOOK.md

Not required — this is internal DB tooling, not user-facing behaviour. Runtime verification of the reset-demo-data CronJob and seed-test-data Job is correctly deferred to Shedward (UAT regression), per the PR checklist.

Minor (non-blocking, pre-existing): the file header still reads "Intended for local development only. Never run against production." while this path runs in the groombook namespace via the reset CronJob — worth tidying in a future pass, out of scope here.

Approving. Reassigning GRO-2672 back to @gb_flea (status todo) for self-merge.

cc @cpfarhood

## QA review (Phase 2, dev→uat) — APPROVED ✅ Reviewed for GRO-2672. Single-file, single-purpose change (`packages/db/src/reset.ts`, +11/-4): swaps drizzle-orm's HWM-based `migrate()` for `execSync("pnpm exec drizzle-kit migrate")`. ### Code correctness — verified against the deploy context `db:reset` is **not** exercised by CI, so I validated the runtime path by hand against the Dockerfile + infra manifests: - **cwd/config resolution ✅** — `reset` runs via `tsx src/reset.ts` (not compiled dist), so `__dirname` = `packages/db/src` and `cwd: resolve(__dirname, "..")` = `packages/db`, exactly where `drizzle.config.ts` lives. drizzle-kit resolves `out: "./migrations"` → `packages/db/migrations`. - **drizzle-kit + pnpm availability ✅** — the reset/seed/migrate images are all `FROM builder`, which runs a full `pnpm install --frozen-lockfile` (devDependencies incl. `drizzle-kit` + `tsx`) before building. pnpm is baked at `/usr/local/bin/pnpm` (GRO-2028) and `HOME=/tmp` is set with a writable emptyDir. The prod-pruned `runner` stage is a different target and isn't used here. - **Matches migrate Job exactly ✅** — the `migrate` stage CMD is `drizzle-kit migrate`; reset now invokes the same hash-based applier, so the reset path and the K8s `migrate-schema` Job apply migrations identically. - **Advisory lock intact ✅** — `postgres(url, { max: 6 })` and the `withSeedAdvisoryLock` invariant (≥2) are preserved; the child `drizzle-kit` process uses its own connection and doesn't contend with the reserved lock session. - **Root-cause coverage ✅** — fixing reset.ts (so it leaves a fully-migrated schema instead of a rolled-back empty DB) also resolves the downstream `seed-test-data` `relation "services" does not exist` reconcile loop; no seed.ts change needed. ### CI All 6 checks green on head `f7f90a7` (Lint & Typecheck, Test, Build & Push Docker Images — push + pull_request). ### UAT_PLAYBOOK.md Not required — this is internal DB tooling, not user-facing behaviour. Runtime verification of the `reset-demo-data` CronJob and `seed-test-data` Job is correctly deferred to Shedward (UAT regression), per the PR checklist. Minor (non-blocking, pre-existing): the file header still reads "Intended for local development only. Never run against production." while this path runs in the `groombook` namespace via the reset CronJob — worth tidying in a future pass, out of scope here. Approving. Reassigning GRO-2672 back to @gb_flea (status `todo`) for self-merge. cc @cpfarhood
Flea Flicker merged commit 89013f29ce into uat 2026-08-06 09:33:46 +00:00
Sign in to join this conversation.