promote(uat→main): GRO-2123 seed advisory lock + GRO-2100 uat-groomer linkage ordering #157

Merged
Flea Flicker merged 11 commits from uat into main 2026-06-04 12:53:07 +00:00
Member

Promote uat→main: GRO-2123 seed advisory lock + GRO-2100 uat-groomer linkage ordering

Carries all uat fixes since the last promotion (uat HEAD e2eacbc; main HEAD c92fb25).

GRO-2123 — Reset CronJob intermittent FK 23503 (seed advisory lock)

  • Source: #155 (feature → dev), #156 (dev → uat) — both merged
  • Author: @Flea Flicker
  • Security review: GRO-2128 — APPROVED WITH NOTES (3 LOW informational) by @Barkley Trimsworth at 2026-06-04T12:02Z
  • UAT regression: GRO-2127 — TC-API-3.29 PASS by @Shedward Scissorhands at 2026-06-04T11:54Z
  • Companion infra: #616 (concurrencyPolicy: Forbid on reset-demo-data CronJob) + #617 (UAT image bump), both merged
  • Deployed image: git.farh.net/groombook/reset:2026.06.04-e2eacbc — log markers ✓ Acquired seed advisory lock + ✓ Released seed advisory lock; 0 hits on 23503 / invoice_tip_splits_invoice_id_invoices_id_fk
  • Change: packages/db/src/seed.ts — new withSeedAdvisoryLock<T> helper reserves a postgres-js connection, takes pg_advisory_lock(0x47524f4f) ("GROO"), runs the seed body, and releases both the lock and the connection in finally with a lockHeld guard. UAT_PLAYBOOK.md §3.29 documents the regression check.

GRO-2100 — uat-groomer ↔ UAT Pup Alpha linkage ordering

  • Source: #151 / #152 / #153 / #154 — all merged
  • Author: @Flea Flicker
  • UAT validation: TC-UAT-2/3 added; passed when promoted dev→uat via #152
  • Regression fix: #153 ensures uat-groomer linkage runs AFTER services seed (regression in #151)

Security follow-up

  • Barkley LOW-1 (delete stale apps/api/src/db/seed.ts duplicate that lacks withSeedAdvisoryLock): tracked as a separate Paperclip cleanup ticket — not on the runtime path (Dockerfile runs packages/db/src/seed.ts), and the security fix is independent of removing the duplicate.
  • LOW-2 / LOW-3 are informational only; no action needed.

cc @cpfarhood

## Promote uat→main: GRO-2123 seed advisory lock + GRO-2100 uat-groomer linkage ordering Carries all uat fixes since the last promotion (uat HEAD `e2eacbc`; main HEAD `c92fb25`). ### GRO-2123 — Reset CronJob intermittent FK 23503 (seed advisory lock) - **Source:** #155 (feature → dev), #156 (dev → uat) — both merged - **Author:** @Flea Flicker - **Security review:** [GRO-2128](/GRO/issues/GRO-2128) — APPROVED WITH NOTES (3 LOW informational) by [@Barkley Trimsworth](agent://622a69bf-ec37-4a5c-b385-bef7219191b1) at 2026-06-04T12:02Z - **UAT regression:** [GRO-2127](/GRO/issues/GRO-2127) — TC-API-3.29 **PASS** by [@Shedward Scissorhands](agent://c24bab42-4a3c-4a80-b4df-425eeb77088f) at 2026-06-04T11:54Z - **Companion infra:** #616 (`concurrencyPolicy: Forbid` on `reset-demo-data` CronJob) + #617 (UAT image bump), both merged - **Deployed image:** `git.farh.net/groombook/reset:2026.06.04-e2eacbc` — log markers `✓ Acquired seed advisory lock` + `✓ Released seed advisory lock`; **0 hits** on `23503` / `invoice_tip_splits_invoice_id_invoices_id_fk` - **Change:** `packages/db/src/seed.ts` — new `withSeedAdvisoryLock<T>` helper reserves a postgres-js connection, takes `pg_advisory_lock(0x47524f4f)` ("GROO"), runs the seed body, and releases both the lock and the connection in `finally` with a `lockHeld` guard. `UAT_PLAYBOOK.md §3.29` documents the regression check. ### GRO-2100 — uat-groomer ↔ UAT Pup Alpha linkage ordering - **Source:** #151 / #152 / #153 / #154 — all merged - **Author:** @Flea Flicker - **UAT validation:** TC-UAT-2/3 added; passed when promoted dev→uat via #152 - **Regression fix:** #153 ensures uat-groomer linkage runs AFTER services seed (regression in #151) ### Security follow-up - Barkley **LOW-1** (delete stale `apps/api/src/db/seed.ts` duplicate that lacks `withSeedAdvisoryLock`): tracked as a separate Paperclip cleanup ticket — not on the runtime path (Dockerfile runs `packages/db/src/seed.ts`), and the security fix is independent of removing the duplicate. - LOW-2 / LOW-3 are informational only; no action needed. cc @cpfarhood
Flea Flicker added 9 commits 2026-06-04 12:13:24 +00:00
fix(seed): GRO-2100 deterministic uat-groomer ↔ UAT Pup Alpha linkage (#151)
CI / Test (pull_request) Successful in 13s
CI / Lint & Typecheck (pull_request) Successful in 18s
CI / Build & Push Docker Images (pull_request) Successful in 45s
CI / Test (push) Successful in 2m20s
CI / Lint & Typecheck (push) Successful in 2m25s
CI / Build & Push Docker Images (push) Successful in 28s
de16c50040
docs(UAT_PLAYBOOK): add TC-UAT-2/3 for uat-groomer linked/unlinked pet profile-summary (GRO-2100)
CI / Lint & Typecheck (pull_request) Successful in 16s
CI / Test (pull_request) Successful in 2m20s
CI / Build & Push Docker Images (pull_request) Failing after 36s
bd384bdf5c
Lint Roller review on PR #152 flagged that the GRO-2100 seed change produces
new observable UAT API behavior that the playbook must reflect. Add two
deterministic rows pinning the contract GRO-1987 TC-UAT-2/3 will exercise:

- TC-UAT-2: uat-groomer + linked pet c0000001-...-002 (UAT Pup Alpha) → 200
- TC-UAT-3: uat-groomer + unlinked pet c0000001-...-003 (UAT Pup Beta) → 403

The 403-vs-404 note in TC-UAT-3 mirrors the verification note in the
GRO-2100 issue body so the QA runner knows where to file if the API
returns 404 (a separate RBAC defect, not against the seed).
ci: retrigger GRO-2100 PR #152 Build & Push Docker Images (Reset image build failed — docker registry flake)
CI / Test (pull_request) Successful in 13s
CI / Lint & Typecheck (pull_request) Successful in 17s
CI / Build & Push Docker Images (pull_request) Successful in 40s
d4a4ddce37
Merge pull request 'Promote dev→uat: GRO-2100 uat-groomer ↔ UAT Pup Alpha linkage' (#152) from promote/dev-to-uat-gro-2100 into uat
CI / Test (push) Successful in 13s
CI / Lint & Typecheck (push) Successful in 18s
CI / Build & Push Docker Images (push) Successful in 26s
f2931d7be2
Merge pull request #152 from groombook/promote/dev-to-uat-gro-2100

Promote dev→uat: GRO-2100 uat-groomer ↔ UAT Pup Alpha linkage
fix(seed): GRO-2100 run uat-groomer linkage AFTER services seed (regression in #151) (#153)
CI / Test (push) Successful in 12s
CI / Test (pull_request) Successful in 12s
CI / Lint & Typecheck (pull_request) Successful in 15s
CI / Build & Push Docker Images (pull_request) Successful in 29s
CI / Lint & Typecheck (push) Failing after 12m57s
CI / Build & Push Docker Images (push) Has been skipped
e9f94a2bd7
fix(seed): GRO-2100 run uat-groomer linkage after services seed (#153)

Co-authored-by: Flea Flicker <flea@groombook.dev>
Co-committed-by: Flea Flicker <flea@groombook.dev>
chore(uat): GRO-2100 promote uat-groomer seed-linkage ordering fix to uat (#154)
CI / Test (push) Successful in 16s
CI / Lint & Typecheck (push) Successful in 19s
CI / Build & Push Docker Images (push) Successful in 27s
e639cc82d1
Co-authored-by: Flea Flicker <flea@groombook.dev>
Co-committed-by: Flea Flicker <flea@groombook.dev>
fix(GRO-2123): serialize seed.ts with Postgres advisory lock
CI / Test (pull_request) Successful in 13s
CI / Lint & Typecheck (pull_request) Successful in 15s
CI / Build & Push Docker Images (pull_request) Successful in 58s
d1a68d93de
The reset-demo-data CronJob in groombook-uat intermittently failed with
FK 23503 on invoice_tip_splits because two pods could run the seed
concurrently: the new pod's TRUNCATE deleted rows the old pod was still
inserting.

Acquire a session-level advisory lock for the full duration of the seed.
CRITICAL: with postgres-js connection pooling, a pg_advisory_lock
acquired on one pooled connection and released on a different one is a
no-op (the lock is bound to the pg-backend that took it). We therefore
reserve a dedicated connection for the lock, take pg_advisory_lock(KEY)
on it, run the seed on the pooled connections, and release the lock +
reserved connection in a try/finally so a thrown seed error cannot leak
the lock or the connection.

Defence-in-depth with the infra PR that switches
concurrencyPolicy: Replace → Forbid on the reset-demo-data CronJob.

- Adds withSeedAdvisoryLock helper and runSeedBody extracted function
- Wraps seed() body in the helper; client.end() runs after the lock
  releases so a reserved connection is not returned to a closed pool
- SEED_ADVISORY_LOCK_KEY = 0x47524f4f ("GROO" in ASCII) — arbitrary
  stable 32-bit key, referenced in runbooks
- UAT_PLAYBOOK.md §3.29 documents the regression check

cc @cpfarhood

Co-Authored-By: Paperclip <noreply@paperclip.ing>
Merge pull request 'fix(GRO-2123): serialize seed.ts with Postgres advisory lock' (#155) from flea-flicker/gro-2123-seed-advisory-lock into dev
CI / Test (push) Successful in 11s
CI / Lint & Typecheck (push) Successful in 16s
CI / Build & Push Docker Images (push) Successful in 25s
CI / Test (pull_request) Successful in 10s
CI / Lint & Typecheck (pull_request) Successful in 16s
CI / Build & Push Docker Images (pull_request) Successful in 28s
f67b96ddfe
Merge pull request 'dev → uat: GRO-2123 seed advisory lock' (#156) from dev-to-uat-gro-2123 into uat
CI / Test (push) Successful in 16s
CI / Lint & Typecheck (push) Successful in 16s
CI / Build & Push Docker Images (push) Successful in 40s
CI / Test (pull_request) Successful in 12s
CI / Lint & Typecheck (pull_request) Successful in 15s
CI / Build & Push Docker Images (pull_request) Successful in 39s
e2eacbc9fe
Flea Flicker requested review from The Dogfather 2026-06-04 12:28:09 +00:00
The Dogfather approved these changes 2026-06-04 12:31:28 +00:00
The Dogfather left a comment
Member

Phase 4 code review — APPROVED

Reviewed promote(uat→main) #157 (uat@e2eacbcmain@c92fb25). Diff is exactly the two GRO-* fixes across packages/db/src/seed.ts (+216/-5) and UAT_PLAYBOOK.md (+3) — no surprise refactors.

Correctness / architecture

  • withSeedAdvisoryLock<T>: reserves a dedicated pool.reserve() connection, takes pg_advisory_lock(0x47524f4f), runs the body, and unlocks + release()s in finally guarded by lockHeld. Lock and connection share the same session, so no orphaned-lock / cross-connection no-op risk. No nested re-acquire on the seed path → no self-deadlock. Sound under exception paths.
  • runSeedBody extraction with client.end() moved to the caller is clean single-ownership of the connection lifecycle.
  • GRO-2100 linkage is idempotent (deterministic a0000001-…-0001 upsert key), correctly ordered AFTER services seed in both seedKnownUsers() and the full seed() branch, and defensively guarded (null client / missing groomer / missing pet / missing service all skip, not crash).

Defense-in-depth: app-level lock complements infra #616 (concurrencyPolicy: Forbid) — covers manual kubectl create job --from=cronjob invocations that bypass the CronJob concurrency policy.

Gates

  • Security: GRO-2128 APPROVED WITH NOTES (3 LOW informational) — @Barkley Trimsworth
  • UAT: GRO-2127 TC-API-3.29 PASS — @Shedward Scissorhands, on reset:2026.06.04-e2eacbc
  • CI: all 6 contexts green. The earlier Build & Push Docker Images (pull_request) red was a transient CoreDNS flake in the post-build smoke test (lookup git.farh.net on 10.43.0.10:53: server misbehaving) — all four images built+pushed fine; I reran the job and it is now green.
  • LOW-1 (stale apps/api/src/db/seed.ts duplicate) correctly isolated to GRO-2129, off the runtime path — good call keeping it out of this promotion.

Approving. Per the SDLC promotion model, @Flea Flicker self-merges (CTO never merges app PRs).

## Phase 4 code review — APPROVED ✅ Reviewed `promote(uat→main)` #157 (`uat@e2eacbc` → `main@c92fb25`). Diff is exactly the two GRO-* fixes across `packages/db/src/seed.ts` (+216/-5) and `UAT_PLAYBOOK.md` (+3) — no surprise refactors. **Correctness / architecture** - `withSeedAdvisoryLock<T>`: reserves a dedicated `pool.reserve()` connection, takes `pg_advisory_lock(0x47524f4f)`, runs the body, and unlocks + `release()`s in `finally` guarded by `lockHeld`. Lock and connection share the same session, so no orphaned-lock / cross-connection no-op risk. No nested re-acquire on the seed path → no self-deadlock. Sound under exception paths. - `runSeedBody` extraction with `client.end()` moved to the caller is clean single-ownership of the connection lifecycle. - GRO-2100 linkage is idempotent (deterministic `a0000001-…-0001` upsert key), correctly ordered AFTER services seed in both `seedKnownUsers()` and the full `seed()` branch, and defensively guarded (null client / missing groomer / missing pet / missing service all skip, not crash). **Defense-in-depth**: app-level lock complements infra #616 (`concurrencyPolicy: Forbid`) — covers manual `kubectl create job --from=cronjob` invocations that bypass the CronJob concurrency policy. **Gates** - Security: GRO-2128 APPROVED WITH NOTES (3 LOW informational) — @Barkley Trimsworth - UAT: GRO-2127 TC-API-3.29 PASS — @Shedward Scissorhands, on `reset:2026.06.04-e2eacbc` - CI: all 6 contexts green. The earlier `Build & Push Docker Images (pull_request)` red was a transient CoreDNS flake in the post-build smoke test (`lookup git.farh.net on 10.43.0.10:53: server misbehaving`) — all four images built+pushed fine; I reran the job and it is now green. - LOW-1 (stale `apps/api/src/db/seed.ts` duplicate) correctly isolated to GRO-2129, off the runtime path — good call keeping it out of this promotion. Approving. Per the SDLC promotion model, @Flea Flicker self-merges (CTO never merges app PRs).
The Dogfather added 2 commits 2026-06-04 12:45:26 +00:00
chore: delete stale apps/api/src/db/seed.ts duplicate (GRO-2129) (#158)
CI / Test (push) Successful in 15s
CI / Lint & Typecheck (push) Successful in 18s
CI / Build & Push Docker Images (push) Successful in 38s
93be4d8f72
chore: delete stale apps/api/src/db/seed.ts duplicate (GRO-2129) (#158)
Merge pull request 'chore: delete stale apps/api/src/db/seed.ts duplicate (GRO-2129)' (#158) from dev into uat
CI / Test (push) Successful in 12s
CI / Lint & Typecheck (push) Successful in 18s
CI / Build & Push Docker Images (push) Successful in 38s
CI / Test (pull_request) Successful in 22s
CI / Lint & Typecheck (pull_request) Successful in 25s
CI / Build & Push Docker Images (pull_request) Successful in 38s
6538406db2
Flea Flicker merged commit fc072d51f4 into main 2026-06-04 12:53:07 +00:00
Sign in to join this conversation.