promote(uat): GRO-2013 owner-bypass + GRO-2033 idempotent migrations (dev→uat) #142

Merged
The Dogfather merged 6 commits from dogfather/gro-2013-promote-uat into uat 2026-06-01 20:14:15 +00:00
Member

Promote dev → uat

Carries the following dev-merged changes to UAT for validation:

GRO-2013 — customer owner-bypass for pet profile-summary (primary)

  • Deployed src/routes/pets.ts: resolveImpersonationClientId helper + owner-bypass gating (isGroomer && !isOwner). Owner-bypass strictly requires session.clientId === pet.clientId; cross-tenant access stays blocked; expired/ended/unknown sessions rejected.
  • Reconciled src/__tests__/petProfileSummary.test.ts (20 tests: 7 GRO-2014 error-handling + 13 GRO-2013 owner-bypass).
  • Dead-tree apps/api/... mirror + UAT_PLAYBOOK.md TC-API-3.19a/b/c.
  • Reviewed: QA (Lint Roller) approved twice on PR #139; CTO logic review passed. CI green.

GRO-2033 — idempotent pet-profile migrations (rides along on dev)

  • packages/db/migrations/0039_extend_pet_profile_columns_idempotent.sql (idempotent)
  • packages/db/migrations/0040_register_missing_coat_type_values.sql (idempotent)
  • _journal.json re-registration.
  • Additive + idempotent; merged to dev via PR #140. Must pass through UAT before any prod promotion. UAT reset/seed (hourly) will exercise these — monitor for seed/reset failures.

GRO-2014 error-handling content is already on uat (PR #138 squash 23484dc).

cc @cpfarhood

## Promote dev → uat Carries the following dev-merged changes to UAT for validation: ### GRO-2013 — customer owner-bypass for pet profile-summary (primary) - Deployed `src/routes/pets.ts`: `resolveImpersonationClientId` helper + owner-bypass gating (`isGroomer && !isOwner`). Owner-bypass strictly requires `session.clientId === pet.clientId`; cross-tenant access stays blocked; expired/ended/unknown sessions rejected. - Reconciled `src/__tests__/petProfileSummary.test.ts` (20 tests: 7 GRO-2014 error-handling + 13 GRO-2013 owner-bypass). - Dead-tree `apps/api/...` mirror + `UAT_PLAYBOOK.md` TC-API-3.19a/b/c. - **Reviewed:** QA (Lint Roller) approved twice on PR #139; CTO logic review passed. CI green. ### GRO-2033 — idempotent pet-profile migrations (rides along on dev) - `packages/db/migrations/0039_extend_pet_profile_columns_idempotent.sql` (idempotent) - `packages/db/migrations/0040_register_missing_coat_type_values.sql` (idempotent) - `_journal.json` re-registration. - Additive + idempotent; merged to dev via PR #140. Must pass through UAT before any prod promotion. UAT reset/seed (hourly) will exercise these — monitor for seed/reset failures. GRO-2014 error-handling content is already on uat (PR #138 squash `23484dc`). cc @cpfarhood
The Dogfather added 6 commits 2026-06-01 20:11:21 +00:00
fix(api): GRO-2014 — profile-summary 500 → 404/401/JSON-500 (#137)
CI / Lint & Typecheck (push) Successful in 16s
CI / Test (push) Successful in 16s
CI / Build & Push Docker Images (push) Successful in 46s
CI / Test (pull_request) Successful in 12s
CI / Lint & Typecheck (pull_request) Successful in 14s
CI / Build & Push Docker Images (pull_request) Failing after 18s
fee62c895d
fix(pets): customer can view own pet profile summary (GRO-2013) (#135)
CI / Test (push) Successful in 16s
CI / Lint & Typecheck (push) Failing after 14m15s
CI / Build & Push Docker Images (push) Has been skipped
9903b51931
Adds an owner-bypass in the profile-summary handler for customers signed in via Better Auth, using the existing X-Impersonation-Session-Id portal session header. When a groomer-role staff row carries a valid impersonation session whose clientId matches the pet's clientId, skip groomerLinkageCheck and serve the summary. Otherwise fall through to the existing linkage check.

Resolves a 403 Forbidden where the customer (auto-provisioned by resolveStaffMiddleware as a 'groomer' staff row with no appointment linkage) could not read their own pet's profile.

Scope: GRO-2013 profile-summary endpoint only — no rbac.ts/schema/Dockerfile changes.

Tests: 6 new cases (owner-bypass, no-header, cross-tenant, expired, manager regression, linked-groomer regression); 294/294 pass.

UAT_PLAYBOOK.md: TC-API-3.19a/b/c.

Closes GRO-2013.

Co-authored-by: The Dogfather <20+gb_dogfather@noreply.git.farh.net>
Co-committed-by: The Dogfather <20+gb_dogfather@noreply.git.farh.net>
fix(db): re-register 0034/0036 schema changes via idempotent 0039/0040 (GRO-2033)
CI / Test (pull_request) Successful in 12s
CI / Lint & Typecheck (pull_request) Successful in 16s
CI / Build & Push Docker Images (pull_request) Successful in 1m11s
27accb9b39
Prod cumulative promotion 2026.06.01-7667288 (PR #596) revealed that
0034_extend_pet_profile_columns (temperament_score + 3 jsonb cols) and
0036_add_missing_coat_type_values (short/medium/silky) were silently
skipped on the prod database, leaving the seed/reset path with:

  Seed failed: PostgresError: column "temperament_score" does not exist

## Root cause: drizzle high-water-mark, same shape as GRO-1999

drizzle-orm@0.38.4 `pg-core/dialect.js#migrate` only applies a journal
entry when its `folderMillis` is strictly greater than the most recent
`__drizzle_migrations.created_at`:

  if (!lastDbMigration || Number(lastDbMigration.created_at) < migration.folderMillis) {
    // apply SQL + record hash
  }

`packages/db/migrations/meta/_journal.json` has 0033's when at
1779500000000 (2026-05-23) — but 0034 was registered with when
1751140800000 (2025-06-28) and 0036 with 1751480000000 (2025-07-02).
Both are below the 0033 watermark, so on the prod DB (whose newest
applied migration was 0033) drizzle silently skipped 0034 and 0036.
0038 (when 1780000000000) was above the watermark, so it applied — and
the migrate Job exits 0 with 'migrations applied successfully!'. The
schema didn't change. GRO-1999 documented the same bug for 0037 → 0038.

UAT/dev are unaffected because their watermarks were already below the
0034/0036 entries when those originally ran.

## Fix

Add two new idempotent migrations with monotonic 'when':

- 0039_extend_pet_profile_columns_idempotent.sql, when 1780000000001:
    ALTER TABLE pets ADD COLUMN IF NOT EXISTS temperament_score integer;
    -- + temperament_flags jsonb, medical_alerts jsonb, preferred_cuts jsonb
- 0040_register_missing_coat_type_values.sql, when 1780000000002:
    ALTER TYPE coat_type ADD VALUE IF NOT EXISTS 'short';
    -- + 'medium', 'silky'

Both are 'IF NOT EXISTS' — safe no-ops on UAT/dev where 0034/0036
applied normally, and effective forward-fix on prod where they were
skipped. Do NOT modify 0034/0036 in place (per the GRO-1999 pattern):
UAT/dev have already applied them and re-running would fail.

## Verification

- packages/db/migrations/meta/_journal.json now has 41 entries with idx
  39 and 40 strictly monotonic in 'when'.
- python3 -c 'import json; json.load(open(...))' parses cleanly.
- ALTER TYPE ADD VALUE IF NOT EXISTS is permitted inside a tx on
  PostgreSQL 18.3 (prod cluster image confirmed via CNPG status).

## UAT Playbook

No user-visible behaviour change — schema only. Existing TC-API-3.8 / 3.9 /
3.11 / 3.13 (extended pet profile) and 3.19a (profile summary) continue to
pass and now ALSO act as smoke tests after the prod image roll-forward.

## Refs

- Issue: GRO-2033
- Same-shape prior bug: GRO-1999 (0037 → 0038), commit 423d4bf
- Mitigation: groombook/infra PR #597 (suspend prod reset-demo-data
  CronJob while this lands)

Co-Authored-By: Paperclip <noreply@paperclip.ing>
Merge pull request 'fix(db): re-register 0034/0036 schema changes via idempotent 0039/0040 (GRO-2033)' (#140) from flea/gro-2033-idempotent-pet-profile-migrations into dev
CI / Test (push) Successful in 12s
CI / Lint & Typecheck (push) Failing after 14m2s
CI / Build & Push Docker Images (push) Has been skipped
4322fb2a00
Merge PR #140: fix(db): re-register 0034/0036 schema changes via idempotent 0039/0040 (GRO-2033)
fix(pets): port owner-bypass into deployed tree (GRO-2013) (#139)
CI / Test (push) Successful in 13s
CI / Lint & Typecheck (push) Successful in 16s
CI / Build & Push Docker Images (push) Successful in 1m5s
CI / Test (pull_request) Successful in 16s
CI / Lint & Typecheck (pull_request) Successful in 2m25s
CI / Build & Push Docker Images (pull_request) Failing after 32s
a2b09ba502
promote(uat): GRO-2013 owner-bypass + GRO-2033 idempotent migrations (dev→uat)
CI / Test (pull_request) Successful in 11s
CI / Lint & Typecheck (pull_request) Successful in 16s
CI / Build & Push Docker Images (pull_request) Successful in 41s
16c959434b
Merge dev into uat. Resolves test-file/playbook conflicts created by PR #138's
squash merge by taking dev's superset versions (verified: all GRO-2014 tests +
TC ids preserved, plus GRO-2013 additions). No-ff merge so dev becomes an
ancestor of uat, preventing future squash-divergence conflicts.

Carries:
- GRO-2013 deployed-tree owner-bypass (src/routes/pets.ts, reconciled 20-test file)
- GRO-2033 idempotent migrations 0039/0040

Co-Authored-By: Paperclip <noreply@paperclip.ing>
The Dogfather merged commit 4e9c4c5e08 into uat 2026-06-01 20:14:15 +00:00
Sign in to join this conversation.