fix(seed): GRO-2100 deterministic uat-groomer ↔ UAT Pup Alpha linkage #151

Merged
Flea Flicker merged 3 commits from flea/gro-2100-uat-groomer-pet-linkage into dev 2026-06-02 18:09:32 +00:00
Member

Problem

The UAT seed creates the uat-groomer@groombook.dev Better Auth account (sign-in works, HTTP 200) but zero appointments link this staffId to any pet, so:

  • GET /api/pets?groomer=me returns []
  • GET /api/pets/{anyId}/profile-summary returns 404 (no linkage)
  • GRO-1987 TC-UAT-2/3 (RBAC tests for the pet profile-summary endpoint) cannot run

This is the seed-side counterpart of GRO-1983 (stale password hashes) — that was the credential row, this is the linkage row.

Fix

Add seedUatGroomerLinkage() in packages/db/src/seed.ts, called from seedUatStaffAccounts() so both the full seed() path and seedKnownUsers() path (prod reset CronJob with SEED_KNOWN_USERS_ONLY=true) produce the deterministic appointment.

The function:

  • Only runs if the uat-groomer staff record exists (graceful no-op for dev seeds without SEED_UAT_STAFF_OIDC_SUB)
  • Skips if the deterministic appointment row already exists (idempotent across hourly reset-demo-data CronJob runs)
  • Picks the "Bath & Brush" service id (b0000001-…-0001) with a fallback to any active service
  • Schedules a completed appointment 7 days ago so recentGroomingHistory reliably includes it
  • Uses deterministic appointment id a0000001-0000-0000-0000-000000000001 as the upsert key

Deterministic contract for GRO-1987

Pet petId Expected uat-groomer response
LINKED — UAT Pup Alpha c0000001-0000-0000-0000-000000000002 200 with recentGroomingHistory[] / visitCount / upcomingAppointment
UNLINKED — UAT Pup Beta c0000001-0000-0000-0000-000000000003 403

If the unlinked case returns 404 instead of 403, that's a separate RBAC defect in the api repo, not the seed.

Verification

After the next 17:00 reset-demo-data CronJob (UAT) or prod reset (after CTO approval):

# TC-UAT-2 — should return 200
curl -b cookies.txt https://uat.groombook.dev/api/pets/c0000001-0000-0000-0000-000000000002/profile-summary

# TC-UAT-3 — should return 403
curl -b cookies.txt https://uat.groombook.dev/api/pets/c0000001-0000-0000-0000-000000000003/profile-summary

cc @cpfarhood

Linked

  • Blocks: GRO-1987 (TC-UAT-2/3 retest)
  • Same family as: GRO-1983 (closed — credential hashes), GRO-2019 (closed — env var snapshot)
  • UAT env: image tag 2026.06.02-411c42b (per GRO-2074 evidence)
## Problem The UAT seed creates the `uat-groomer@groombook.dev` Better Auth account (sign-in works, HTTP 200) but **zero appointments link this staffId to any pet**, so: - `GET /api/pets?groomer=me` returns `[]` - `GET /api/pets/{anyId}/profile-summary` returns 404 (no linkage) - GRO-1987 TC-UAT-2/3 (RBAC tests for the pet profile-summary endpoint) cannot run This is the seed-side counterpart of GRO-1983 (stale password hashes) — that was the credential row, this is the linkage row. ## Fix Add `seedUatGroomerLinkage()` in `packages/db/src/seed.ts`, called from `seedUatStaffAccounts()` so both the full `seed()` path and `seedKnownUsers()` path (prod reset CronJob with `SEED_KNOWN_USERS_ONLY=true`) produce the deterministic appointment. The function: - Only runs if the `uat-groomer` staff record exists (graceful no-op for dev seeds without `SEED_UAT_STAFF_OIDC_SUB`) - Skips if the deterministic appointment row already exists (idempotent across hourly `reset-demo-data` CronJob runs) - Picks the `"Bath & Brush"` service id (`b0000001-…-0001`) with a fallback to any active service - Schedules a completed appointment 7 days ago so `recentGroomingHistory` reliably includes it - Uses deterministic appointment id `a0000001-0000-0000-0000-000000000001` as the upsert key ## Deterministic contract for GRO-1987 | Pet | petId | Expected uat-groomer response | |---|---|---| | LINKED — UAT Pup Alpha | `c0000001-0000-0000-0000-000000000002` | 200 with `recentGroomingHistory[]` / `visitCount` / `upcomingAppointment` | | UNLINKED — UAT Pup Beta | `c0000001-0000-0000-0000-000000000003` | 403 | If the unlinked case returns 404 instead of 403, that's a separate RBAC defect in the api repo, not the seed. ## Verification After the next 17:00 `reset-demo-data` CronJob (UAT) or prod reset (after CTO approval): ``` # TC-UAT-2 — should return 200 curl -b cookies.txt https://uat.groombook.dev/api/pets/c0000001-0000-0000-0000-000000000002/profile-summary # TC-UAT-3 — should return 403 curl -b cookies.txt https://uat.groombook.dev/api/pets/c0000001-0000-0000-0000-000000000003/profile-summary ``` cc @cpfarhood ## Linked - Blocks: [GRO-1987](/GRO/issues/GRO-1987) (TC-UAT-2/3 retest) - Same family as: [GRO-1983](/GRO/issues/GRO-1983) (closed — credential hashes), [GRO-2019](/GRO/issues/GRO-2019) (closed — env var snapshot) - UAT env: image tag `2026.06.02-411c42b` (per GRO-2074 evidence)
Flea Flicker added 2 commits 2026-06-02 17:41:53 +00:00
fix(db): make services seed idempotent across resets (GRO-2064, GRO-2033 close-out)
CI / Test (pull_request) Successful in 13s
CI / Lint & Typecheck (pull_request) Successful in 16s
CI / Build & Push Docker Images (pull_request) Successful in 1m20s
fcd4c0bf48
The seed Job `seed-test-data-b5943fb` failed three times on prod with
`duplicate key value violates unique constraint "services_pkey"` after
migrations 0039/0040 landed. Two interlocking bugs in
`packages/db/src/seed.ts` (and the parallel `apps/api/src/db/seed.ts`
tree — both kept in sync per the GRO-2052/2013/2014 lesson):

1. The reset `TRUNCATE` excluded `services`, so a prior
   `seedKnownUsers` run that wrote `id=b0000001-…-004, name="Nail Trim"`
   survived every reset. The next full `seed()` then tried to insert
   `id=b0000001-…-004, name="Full Groom — Large"` and PostgreSQL
   raised `services_pkey` (id collision) — the name-targeted
   `ON CONFLICT` couldn't fire because the conflict was on a different
   column.
2. The `demoSvcs` (used by `seedKnownUsers`) had `id=…-004, name="Nail Trim"`
   while `servicesDef` (used by the full `seed()`) has `id=…-004,
   name="Full Groom — Large"`. `Nail Trim` was supposed to be
   `id=…-005` in the demo subset.

Fix:
  * `TRUNCATE services, …` so each reset rebuilds the catalogue from
    `servicesDef` (CASCADE handles appointments/invoices FKs).
  * Key both services upserts on `schema.services.id` (not `name`) so
    deterministic ids always win — defense-in-depth if a future change
    drops `services` from the TRUNCATE list again.
  * Reconcile the id↔name map: `demoSvcs[3]` is now
    `id=…-005, name="Nail Trim"` to match `servicesDef[4]`.
  * Update `UAT_PLAYBOOK.md §4.5.1` with regression coverage
    (TC-SEED-1..4).

Required for the GRO-2033 close-out: infra PR #605 must repoint to the
new image tag (NOT 2a6242d) and `apps/overlays/prod/reset-cronjob.yaml`
must stay suspended until a one-shot seed Job runs 1/1 against prod.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
fix(seed): GRO-2100 deterministic uat-groomer ↔ UAT Pup Alpha linkage
CI / Test (pull_request) Successful in 10s
CI / Lint & Typecheck (pull_request) Failing after 12m51s
CI / Build & Push Docker Images (pull_request) Has been skipped
8fb6c9375b
The UAT seed creates the uat-groomer@groombook.dev Better Auth account
(staffId 00000000-0000-0000-0000-000000000004) but no appointments, so
GET /api/pets?groomer=me returns [] and GET /api/pets/{anyId}/profile-summary
returns 404. This makes GRO-1987 TC-UAT-2/3 (RBAC tests for the
profile-summary endpoint) un-runnable.

This is the seed-side counterpart of GRO-1983 (stale password hashes):
that was the credential row, this is the linkage row.

Fix: add seedUatGroomerLinkage() called from seedUatStaffAccounts(), so
both the full seed() path and the seedKnownUsers() path (prod reset
CronJob with SEED_KNOWN_USERS_ONLY=true) produce a deterministic
completed appointment linking the UAT groomer to UAT Pup Alpha
(c0000001-0000-0000-0000-000000000002). UAT Pup Beta is intentionally
left UNLINKED so TC-UAT-3 can verify the 403 forbidden response.

The deterministic appointment id (a0000001-0000-0000-0000-000000000001)
makes the function idempotent: re-running the seed (hourly via the
reset-demo-data CronJob) is a no-op once the row exists.

Verification (after the next 17:00 reset):
  - GET /api/pets/{c0000001-0000-0000-0000-000000000002}/profile-summary
    as uat-groomer → 200 with recentGroomingHistory/visitCount/upcomingAppointment
  - GET /api/pets/{c0000001-0000-0000-0000-000000000003}/profile-summary
    as uat-groomer → 403

If the unlinked-pet case returns 404 instead of 403, that is a
separate RBAC defect — file against the api repo, not the seed.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
Lint Roller approved these changes 2026-06-02 17:52:13 +00:00
Lint Roller left a comment
Member

QA review pass. seedUatGroomerLinkage() correctly links uat-groomer to UAT Pup Alpha (c0000001-0000-0000-0000-000000000002) deterministically. UAT Pup Beta left unlinked for TC-UAT-3 (403). Idempotent. GRO-2064 services fix clean. CI Test: passed.

QA review pass. seedUatGroomerLinkage() correctly links uat-groomer to UAT Pup Alpha (c0000001-0000-0000-0000-000000000002) deterministically. UAT Pup Beta left unlinked for TC-UAT-3 (403). Idempotent. GRO-2064 services fix clean. CI Test: passed.
Flea Flicker added 1 commit 2026-06-02 18:06:08 +00:00
ci: retrigger GRO-2100 PR #151 CI (Lint & Typecheck failed at actions/checkout@v4 — runner infra)
CI / Test (pull_request) Successful in 11s
CI / Lint & Typecheck (pull_request) Successful in 15s
CI / Build & Push Docker Images (pull_request) Successful in 1m0s
53579e979d
Flea Flicker merged commit de16c50040 into dev 2026-06-02 18:09:32 +00:00
Sign in to join this conversation.