feat: GRO-2652 boot ECONNRESET resilience + CORS enforcement + OOBE endpoint (uat→main) #226

Merged
Flea Flicker merged 47 commits from uat into main 2026-08-05 11:57:24 +00:00
Member

Summary

Promotes uatmain with the following features validated through UAT and Security review.

GRO-2652 — Boot ECONNRESET resilience (root bug fix)

  • src/index.ts: server starts before initAuth()/health and public routes live immediately; auth initialises in retry loop (up to 10 attempts, 500ms→30s backoff)
  • src/lib/auth.ts: retry-with-backoff (5 attempts, 1s/2s/4s/8s); authInitPromise cleared on failure so retry loop makes real DB attempts
  • Permanent auth failure degrades to 503 on auth routes — no process.exit(1) crash

GRO-2586 — CORS origin allowlist enforcement

  • enforceAuthCors() strips CORS headers for any origin not in TRUSTED_ORIGINS

GRO-2425 — Comma-split CORS_ORIGIN

  • CORS_ORIGIN env var split on commas for multi-origin config

GRO-2359 — POST /api/portal/clients-from-auth OOBE endpoint

  • Creates a client record linked to authenticated Better Auth user

Validation

  • UAT (Shedward): PASS — §4.19 Boot Resilience, §4.20 OOBE clients-from-auth, §3 SSO login, §1 /health all passed
  • Security (Barkley): PASS — CORS allowlist enforcement, 503-degradation, OOBE session-gate reviewed and cleared

UAT Playbook

Updated UAT_PLAYBOOK.md §4.19 (Boot Resilience) and §4.20 (OOBE clients-from-auth).

Linked issues

  • GRO-2652, GRO-2586, GRO-2425, GRO-2359

cc @cpfarhood

## Summary Promotes `uat` → `main` with the following features validated through UAT and Security review. ### GRO-2652 — Boot ECONNRESET resilience (root bug fix) - `src/index.ts`: server starts before `initAuth()` — `/health` and public routes live immediately; auth initialises in retry loop (up to 10 attempts, 500ms→30s backoff) - `src/lib/auth.ts`: retry-with-backoff (5 attempts, 1s/2s/4s/8s); `authInitPromise` cleared on failure so retry loop makes real DB attempts - Permanent auth failure degrades to `503` on auth routes — no `process.exit(1)` crash ### GRO-2586 — CORS origin allowlist enforcement - `enforceAuthCors()` strips CORS headers for any origin not in `TRUSTED_ORIGINS` ### GRO-2425 — Comma-split CORS_ORIGIN - `CORS_ORIGIN` env var split on commas for multi-origin config ### GRO-2359 — `POST /api/portal/clients-from-auth` OOBE endpoint - Creates a client record linked to authenticated Better Auth user ## Validation - **UAT (Shedward):** PASS — §4.19 Boot Resilience, §4.20 OOBE clients-from-auth, §3 SSO login, §1 /health all passed - **Security (Barkley):** PASS — CORS allowlist enforcement, 503-degradation, OOBE session-gate reviewed and cleared ## UAT Playbook Updated `UAT_PLAYBOOK.md` §4.19 (Boot Resilience) and §4.20 (OOBE clients-from-auth). ## Linked issues - GRO-2652, GRO-2586, GRO-2425, GRO-2359 cc @cpfarhood
Flea Flicker added 45 commits 2026-08-05 10:47:15 +00:00
feat(GRO-2225): UAT seed route cohort + receptionist credential (#187)
CI / Test (push) Successful in 30s
CI / Lint & Typecheck (push) Successful in 32s
CI / Build & Push Docker Images (push) Successful in 45s
27e6674b9a
Promote dev→uat: GRO-2225 UAT seed route cohort + receptionist credential
CI / Test (pull_request) Successful in 30s
CI / Lint & Typecheck (pull_request) Successful in 31s
CI / Build & Push Docker Images (pull_request) Failing after 15s
76d9850464
fix(GRO-2235): return 409 on duplicate portal waitlist submit (#189)
CI / Test (push) Failing after 14m19s
CI / Lint & Typecheck (push) Failing after 14m19s
CI / Build & Push Docker Images (push) Has been skipped
6702086c7b
feat(GRO-2157): navigation export endpoints (Phase 2.3) (#190)
CI / Test (push) Successful in 24s
CI / Lint & Typecheck (push) Successful in 40s
CI / Build & Push Docker Images (push) Successful in 26s
cd2f60e282
Merge remote-tracking branch 'origin/dev' into promote/dev-to-uat-gro-2225
CI / Test (pull_request) Failing after 5s
CI / Lint & Typecheck (pull_request) Successful in 28s
CI / Build & Push Docker Images (pull_request) Has been skipped
d617c69571
ci: re-trigger checks (transient pnpm/action-setup runner flake)
CI / Test (pull_request) Successful in 26s
CI / Lint & Typecheck (pull_request) Successful in 30s
CI / Build & Push Docker Images (pull_request) Successful in 27s
37e42b3104
Co-Authored-By: Paperclip <noreply@paperclip.ing>
Merge pull request 'Promote dev→uat: GRO-2225 + GRO-2235 + GRO-2157 (atomic)' (#188) from promote/dev-to-uat-gro-2225 into uat
CI / Test (push) Successful in 29s
CI / Lint & Typecheck (push) Successful in 36s
CI / Build & Push Docker Images (push) Successful in 41s
CI / Test (pull_request) Successful in 28s
CI / Lint & Typecheck (pull_request) Successful in 31s
CI / Build & Push Docker Images (pull_request) Successful in 1m17s
4868f18dfd
Promote dev→uat: GRO-2225 + GRO-2235 + GRO-2157 (atomic)

QA-approved on 37e42b3. CI green (Test, Lint & Typecheck, Build & Push).
GRO-2294: Route Optimization security hardening (geocode-batch limit cap + redact settings secret) (#193)
CI / Test (push) Successful in 27s
CI / Lint & Typecheck (push) Successful in 35s
CI / Build & Push Docker Images (push) Successful in 38s
fe412933ea
Promote GRO-2294 to UAT: Route Optimization security hardening (#194)
CI / Lint & Typecheck (push) Successful in 28s
CI / Test (push) Successful in 29s
CI / Build & Push Docker Images (push) Successful in 39s
CI / Test (pull_request) Successful in 25s
CI / Lint & Typecheck (pull_request) Successful in 37s
CI / Build & Push Docker Images (pull_request) Successful in 1m8s
2566fb8f20
fix(GRO-2299): redact googleMapsApiKey from PATCH /api/admin/settings response (#195)
CI / Test (push) Successful in 26s
CI / Lint & Typecheck (push) Successful in 30s
CI / Build & Push Docker Images (push) Successful in 38s
b4b48f7b50
dev → uat: GRO-2299 redact googleMapsApiKey from PATCH /api/admin/settings (#196)
CI / Test (push) Failing after 10m55s
CI / Lint & Typecheck (push) Failing after 10m55s
CI / Build & Push Docker Images (push) Has been skipped
8cd5a2ef4d
fix(GRO-2139): serialize reset→migrate→seed under the seed advisory lock (#160)
CI / Test (push) Successful in 24s
CI / Lint & Typecheck (push) Successful in 37s
CI / Build & Push Docker Images (push) Successful in 36s
1e0747324d
Serialize the entire db:reset chain (DROP → migrate → seed) inside one withSeedAdvisoryLock callback so a concurrent same-PRNG seeder cannot interleave and collide on invoices_pkey. Pool sized max:6 (1 reserved for the lock + work headroom) to avoid the connection-starvation deadlock the CTO caught. Verified with three end-to-end live db:reset runs against a throwaway Postgres.

cc @cpfarhood
GRO-2172: add missing extended pet fields to create/update schemas (#199)
CI / Lint & Typecheck (push) Successful in 1m13s
CI / Test (push) Successful in 2m31s
CI / Build & Push Docker Images (push) Successful in 35s
2853ce73a5
dev → uat: GRO-2172 extended pet fields (#200)
CI / Test (push) Successful in 23s
CI / Lint & Typecheck (push) Successful in 24s
CI / Build & Push Docker Images (push) Successful in 39s
c4385617c6
feat(seed): seed upcoming appointments across statuses for UAT portal customer (GRO-2311) (#201)
CI / Test (push) Successful in 31s
CI / Lint & Typecheck (push) Successful in 2m35s
CI / Build & Push Docker Images (push) Successful in 1m25s
d61607f4c5
dev → uat: GRO-2311 seed portal StatusBadge appointments (#201) (#202)
CI / Lint & Typecheck (push) Successful in 28s
CI / Test (push) Successful in 24s
CI / Build & Push Docker Images (push) Successful in 1m23s
807ccb455f
feat(GRO-2319): surface active waitlist entries on portal appointments + seed (#204)
CI / Test (push) Successful in 28s
CI / Lint & Typecheck (push) Successful in 33s
CI / Build & Push Docker Images (push) Successful in 45s
ef18ed7376
feat(GRO-2319): dev→uat — portal waitlist surfacing + seed (api) (#205)
CI / Test (push) Successful in 30s
CI / Lint & Typecheck (push) Successful in 36s
CI / Build & Push Docker Images (push) Successful in 1m15s
18640908ed
fix(GRO-2342): portal waitlist card populates service {id, name}
CI / Test (pull_request) Successful in 26s
CI / Lint & Typecheck (pull_request) Successful in 29s
CI / Build & Push Docker Images (pull_request) Successful in 1m15s
277f459237
Cosmetic follow-up to GRO-2319 (Phase 4 review by CTO). The synthetic
waitlist card on GET /portal/appointments returned service: {id} only,
so the portal fell back to the literal 'Service' label. CMPO spec did
not call for a service name on the waitlist card, but populating the
real name is non-urgent and closes the cosmetic gap.

- src/routes/portal.ts: include a services SELECT (in addition to
  pets and staff) covering both appointment and waitlist serviceIds.
  serviceMap feeds a service.name lookup. The synthetic waitlist
  card's service object is now {id, name} — same shape the
  appointments join returns — so the portal renders the real name.
  The appointments join also gains a name (consistent shape, no
  regression for the existing path).
- src/__tests__/portal.test.ts: mock the services table and assert
  service: {id, name} on both the synthetic waitlist card and the
  appointment card.
- UAT_PLAYBOOK.md: TC-API-8.20 covering the waitlist card service
  name (TC-API-8.19 retained verbatim for the original GRO-2319
  surfacing contract).

Co-Authored-By: Paperclip <noreply@paperclip.ing>
Merge pull request 'fix(GRO-2342): portal waitlist card populates service {id, name}' (#208) from feat/GRO-2342-portal-waitlist-servicename into dev
CI / Test (push) Successful in 28s
CI / Lint & Typecheck (push) Successful in 29s
CI / Build & Push Docker Images (push) Successful in 33s
1d6b906202
Promote dev → uat: GRO-2342 portal waitlist service {id, name}
CI / Test (pull_request) Successful in 25s
CI / Lint & Typecheck (pull_request) Successful in 25s
CI / Build & Push Docker Images (pull_request) Successful in 33s
e932050b45
Resolves conflicts in UAT_PLAYBOOK.md, src/routes/portal.ts, and
src/__tests__/portal.test.ts (dev side wins — GRO-2342 changes are
the only diff in scope). Carries forward GRO-2139 reset.ts advisory
lock + GRO-2294 infra mcp trigger that were merged to dev but not
yet promoted to uat.

- src/routes/portal.ts: GET /portal/appointments now populates
  service: {id, name} on both the synthetic waitlist card and the
  appointment card (was {id} only). Same shape, no portal change
  required.
- src/__tests__/portal.test.ts: services mock + TC-API-8.20 GRO-2342
  assertions on the synthetic waitlist card service name.
- UAT_PLAYBOOK.md: TC-API-8.20 (GRO-2342) appended; TC-API-8.19
  (GRO-2319) retained verbatim.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
Merge pull request 'Promote dev → uat: GRO-2342 portal waitlist service {id, name}' (#209) from promote/dev-to-uat-gro-2342 into uat
CI / Test (push) Successful in 27s
CI / Lint & Typecheck (push) Successful in 30s
CI / Build & Push Docker Images (push) Successful in 26s
CI / Test (pull_request) Successful in 24s
CI / Lint & Typecheck (pull_request) Failing after 10m19s
CI / Build & Push Docker Images (pull_request) Has been skipped
4cc51b32d3
feat(GRO-2359): add POST /api/portal/clients-from-auth for OOBE (web)
CI / Test (pull_request) Successful in 29s
CI / Lint & Typecheck (pull_request) Successful in 41s
CI / Build & Push Docker Images (pull_request) Successful in 1m40s
cdeebec021
The OOBE flow on the web portal calls this endpoint to create a fresh
`clients` row bound to the Better Auth user's email when the SSO
bridge returns 404. Returns 201 on success, 409 if a client with that
email already exists (portal-selection case), 401/503 on auth issues,
400 on invalid body.

The OOBE success path navigates the user back to `/` and lets the
existing `session-from-auth` re-bridge; the new client is now
resolvable by email, so the bridge mints a real portal session.

Tests cover: 401 (no session), 400 (zod), 201 + persisted values
(name trimmed, optional fields normalized to null), 409 (existing
client or unique-constraint race), 503 (auth not configured).

Paired with the web PR on `feature/2357-p2-sso-to-oobe-routing`.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
Merge pull request 'feat(GRO-2359): add POST /api/portal/clients-from-auth for OOBE' (#212) from feature/2357-p2-portal-clients-from-auth into dev
CI / Test (push) Successful in 26s
CI / Lint & Typecheck (push) Successful in 32s
CI / Build & Push Docker Images (push) Successful in 41s
10b78d810d
GRO-2359 (api): feat(GRO-2359): add POST /api/portal/clients-from-auth for OOBE (#212)
feat(GRO-2359): add POST /api/portal/clients-from-auth for OOBE (web)
CI / Test (pull_request) Successful in 28s
CI / Lint & Typecheck (pull_request) Successful in 34s
CI / Build & Push Docker Images (pull_request) Successful in 42s
5363e1d5dc
The OOBE flow on the web portal calls this endpoint to create a fresh
`clients` row bound to the Better Auth user's email when the SSO
bridge returns 404. Returns 201 on success, 409 if a client with that
email already exists (portal-selection case), 401/503 on auth issues,
400 on invalid body.

The OOBE success path navigates the user back to `/` and lets the
existing `session-from-auth` re-bridge; the new client is now
resolvable by email, so the bridge mints a real portal session.

Tests cover: 401 (no session), 400 (zod), 201 + persisted values
(name trimmed, optional fields normalized to null), 409 (existing
client or unique-constraint race), 503 (auth not configured).

Paired with the web PR on `feature/2357-p2-sso-to-oobe-routing`.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
(cherry picked from commit cdeebec021)
Merge pull request 'Promote dev → uat: GRO-2359 clients-from-auth endpoint' (#213) from promote/GRO-2359-dev-to-uat into uat
CI / Test (push) Successful in 27s
CI / Lint & Typecheck (push) Successful in 34s
CI / Build & Push Docker Images (push) Successful in 38s
a629331a04
Promote dev → uat: GRO-2359 clients-from-auth endpoint (#213)
feat(GRO-2425): split CORS_ORIGIN on commas for multiple trusted auth origins (#216)
CI / Test (push) Successful in 30s
CI / Lint & Typecheck (push) Successful in 45s
CI / Build & Push Docker Images (push) Successful in 1m10s
CI / Test (pull_request) Successful in 25s
CI / Lint & Typecheck (pull_request) Failing after 12m18s
CI / Build & Push Docker Images (pull_request) Has been skipped
c01e4acf0a
feat(GRO-2425): split CORS_ORIGIN on commas for multiple trusted auth origins

Co-authored-by: Flea Flicker <flea@groombook.dev>
Co-committed-by: Flea Flicker <flea@groombook.dev>
chore: promote dev → uat (GRO-2425 comma-split CORS_ORIGIN) (#217)
CI / Test (push) Successful in 26s
CI / Lint & Typecheck (push) Successful in 30s
CI / Build & Push Docker Images (push) Successful in 47s
63d7aaa8c2
chore: promote dev → uat (GRO-2425 comma-split CORS_ORIGIN)

Co-authored-by: Flea Flicker <flea@groombook.dev>
Co-committed-by: Flea Flicker <flea@groombook.dev>
fix(GRO-2586): enforce trusted-origins allowlist on Better Auth CORS responses (#219)
CI / Lint & Typecheck (push) Successful in 19s
CI / Test (push) Successful in 21s
CI / Build & Push Docker Images (push) Successful in 1m4s
dace2c4e66
fix(GRO-2586): enforce trusted-origins allowlist on Better Auth CORS responses

Co-Authored-By: Paperclip <noreply@paperclip.ing>
promote(GRO-2586): dev → uat — CORS origin allowlist enforcement (#220)
CI / Lint & Typecheck (push) Successful in 39s
CI / Test (push) Successful in 40s
CI / Build & Push Docker Images (push) Successful in 50s
2d4edb6452
promote(GRO-2586): dev → uat — CORS origin allowlist enforcement
The auth_provider_config DB query at boot has no error handling;
a transient ECONNRESET causes authInitPromise to reject, propagating
to the top-level await initAuth() and crashing the process (exit 1).

Add up to 5 retry attempts with exponential backoff (1 s, 2 s, 4 s, 8 s)
so a single connection reset does not abort initialization.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
Previously: await initAuth() was a top-level ESM await. Any boot-time
ECONNRESET from Postgres propagated as an uncaught module-evaluation
error and killed the process before the server even started.

Now:
- serve() starts immediately so /health and public routes are available
- initAuth() runs in a retry loop (up to 10 attempts, exponential
  500 ms → 30 s); a permanent failure degrades to 503 on auth routes
  (existing catch-to-503 in authRouter) rather than crashing the pod

Co-Authored-By: Paperclip <noreply@paperclip.ing>
test(GRO-2652): add boot resilience UAT test cases (TC-API-19.x)
CI / Test (pull_request) Successful in 25s
CI / Lint & Typecheck (pull_request) Successful in 28s
CI / Build & Push Docker Images (pull_request) Successful in 56s
8d42bfffc9
New section 4.19 verifies:
- /health available before initAuth completes
- auth routes return 503 (not crash) during init retry window
- pod restart count stays stable after deploy
- retry log lines emitted correctly
- auth recovers after transient DB hiccup

Co-Authored-By: Paperclip <noreply@paperclip.ing>
Gitea OCI registry sporadically rejects cache blob writes with
"error writing layer blob: unknown" — this fails the build step
even though the image itself was pushed successfully. Adding
ignore-error=true makes cache write failures non-fatal so the
build proceeds regardless of registry-side cache issues.

Fixes recurring CI failure on Build and push Seed image step.
Merges feature/GRO-2652-boot-econnreset-resilience into dev. CI passed (run #440). Fixes PROD CrashLoopBackOff caused by bare top-level await initAuth() + unhandled ECONNRESET in auth_provider_config DB query.
Co-Authored-By: Paperclip <noreply@paperclip.ing>
docs(GRO-2359): add UAT_PLAYBOOK §4.20 clients-from-auth test cases
CI / Lint & Typecheck (pull_request) Successful in 19s
CI / Test (pull_request) Successful in 21s
CI / Build & Push Docker Images (pull_request) Successful in 1m9s
61f23e47f1
Co-Authored-By: Paperclip <noreply@paperclip.ing>
fix(GRO-2652): restore ci.yml from double-base64 corruption + UAT_PLAYBOOK §4.20 clients-from-auth
CI / Lint & Typecheck (push) Successful in 21s
CI / Test (pull_request) Successful in 21s
CI / Lint & Typecheck (pull_request) Successful in 34s
CI / Test (push) Failing after 40s
CI / Build & Push Docker Images (push) Has been skipped
CI / Build & Push Docker Images (pull_request) Successful in 27s
f98c5ccaf7
fix(GRO-2652): reset authInitPromise on failure so retry loop actually retries
CI / Lint & Typecheck (pull_request) Failing after 10s
CI / Test (pull_request) Failing after 24s
CI / Build & Push Docker Images (pull_request) Has been skipped
ae0ce3824f
Previously the initAuth() function set authInitPromise to the async IIFE's
Promise but never cleared it on rejection. The index.ts retry loop called
initAuth() up to 10 times, but on attempt 2+ the check
`if (authInitPromise) { await authInitPromise; return; }` would immediately
re-throw the original rejection without performing a real retry.

Fix: wrap the final `await authInitPromise` in try/catch and reset
authInitPromise = null on error. This allows the index.ts retry loop to
create a fresh attempt on each call after a failure.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
fix(GRO-2652): reset authInitPromise on failure so retry loop actually retries
CI / Lint & Typecheck (pull_request) Successful in 19s
CI / Test (pull_request) Successful in 21s
CI / Build & Push Docker Images (pull_request) Successful in 53s
a1b27b5501
Previously the initAuth() function set authInitPromise to the async IIFE's
Promise but never cleared it on rejection. The index.ts retry loop called
initAuth() up to 10 times, but on attempt 2+ the check
`if (authInitPromise) { await authInitPromise; return; }` would immediately
re-throw the original rejection without performing a real retry.

Fix: wrap the final `await authInitPromise` in try/catch and reset
authInitPromise = null on error. This allows the index.ts retry loop to
create a fresh attempt on each call after a failure.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
fix(GRO-2652): reset authInitPromise on failure so retry loop actually retries
CI / Lint & Typecheck (push) Successful in 20s
CI / Test (push) Failing after 21s
CI / Build & Push Docker Images (push) Has been skipped
CI / Lint & Typecheck (pull_request) Successful in 18s
CI / Test (pull_request) Failing after 25s
CI / Build & Push Docker Images (pull_request) Has been skipped
f32e9a6889
Merging Phase 1 fix — CI all green (lint, test, build all success). Resolves QA's retry-memoization concern from PR #223 review.
test(GRO-2652): stub fetch in auth tests to fix OIDC discovery timeout flake
CI / Lint & Typecheck (push) Successful in 19s
CI / Test (push) Successful in 22s
CI / Lint & Typecheck (pull_request) Successful in 19s
CI / Test (pull_request) Successful in 20s
CI / Build & Push Docker Images (pull_request) Successful in 52s
CI / Build & Push Docker Images (push) Successful in 1m45s
f1b0a53520
AbortSignal.timeout(5000) in initAuth's discovery fetch races with
vitest's 5000ms default test timeout, causing intermittent failures on CI push
runs. Stub fetch to return ok:false instantly so tests complete in <1s.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
fix(GRO-2652): promote boot ECONNRESET resilience to UAT (dev→uat)
CI / Lint & Typecheck (push) Successful in 19s
CI / Test (push) Successful in 20s
CI / Build & Push Docker Images (push) Successful in 28s
CI / Lint & Typecheck (pull_request) Successful in 23s
CI / Test (pull_request) Successful in 23s
CI / Build & Push Docker Images (pull_request) Successful in 22s
1df834c5bc
Merges boot ECONNRESET resilience fix + CORS enforcement + OOBE endpoint to uat. QA approved (Lint Roller) at #223 (comment)

Co-Authored-By: Paperclip <noreply@paperclip.ing>
Flea Flicker requested review from The Dogfather 2026-08-05 10:47:22 +00:00
The Dogfather requested changes 2026-08-05 10:53:17 +00:00
Dismissed
The Dogfather left a comment
Member

CTO code review (Phase 4, uat→main) — Request changes

The feature code is solid and I approve it in substance. Two out-of-scope artifacts are the only thing blocking merge to main; both are trivial to remove and CI will re-green in ~1 minute. I'll re-approve immediately on the fixed head.

Feature code — approved in substance

  • GRO-2652 boot resilience (src/index.ts, src/lib/auth.ts): server-first startup, bounded retry loop (10 attempts, capped 30s backoff), authInitPromise nulled on failure so retries make real DB attempts, permanent failure degrades auth routes to 503 with no process.exit(1). This is the correct fix for the critical prod crashloop. Good.
  • GRO-2586 CORS enforcement (src/lib/auth-cors.ts): correct defense-in-depth. Better Auth reflects Origin into Access-Control-Allow-Origin independent of Hono's cors() middleware, so wrapping its handler response and stripping CORS headers for non-allowlisted origins is the right layer. TRUSTED_ORIGINS is defined consistently with auth.ts.
  • GRO-2425 comma-split CORS_ORIGIN: consistent in both index.ts and auth.ts. Good.
  • GRO-2359 OOBE POST /api/portal/clients-from-auth: properly session-gated (401 without a Better Auth session), email is server-derived from the session (no client spoofing), 409 pre-check + 23505 race handling, and it does not leak the existing row's PII. Sound.

Required changes (blocking) 🔴

  1. Remove trigger-uat-1779751324.txt. Empty CI-trigger litter — must not land on main.
  2. Remove .mcp.json. This is agent/workspace tooling (a Gitea MCP config carrying a Bearer ${GITEA_TOKEN} auth header). It has no role in the running API service and should not be promoted to the production application repo. If it's needed for local agent work, add it to .gitignore and untrack it rather than committing it.

Non-blocking notes (no action required for this merge)

  • PR body under-describes the promotion. This uat→main batch also carries GRO-2319 (waitlist cards), GRO-2342 (service names), GRO-2235 (waitlist-duplicate 409), the navigationExport service, settings/pets/geocode changes, and a large packages/db/src/seed.ts rewrite — none listed in the summary. That's expected for a uat→main batch, but please confirm those all transited the normal dev→uat pipeline with UAT/Security coverage, and consider listing them so the production changeset is accurately documented.
  • enforceAuthCors could set Vary: Origin when reflecting the origin (cache-correctness best practice). Auth responses are credentialed and typically uncached, so this is a nit, not a blocker.

Once the two files are removed and CI is green, re-request my review and I'll approve on the new head so you can self-merge.

cc @cpfarhood

## CTO code review (Phase 4, uat→main) — Request changes The feature code is solid and I approve it **in substance**. Two out-of-scope artifacts are the only thing blocking merge to `main`; both are trivial to remove and CI will re-green in ~1 minute. I'll re-approve immediately on the fixed head. ### Feature code — approved in substance ✅ - **GRO-2652 boot resilience** (`src/index.ts`, `src/lib/auth.ts`): server-first startup, bounded retry loop (10 attempts, capped 30s backoff), `authInitPromise` nulled on failure so retries make real DB attempts, permanent failure degrades auth routes to 503 with no `process.exit(1)`. This is the correct fix for the critical prod crashloop. Good. - **GRO-2586 CORS enforcement** (`src/lib/auth-cors.ts`): correct defense-in-depth. Better Auth reflects `Origin` into `Access-Control-Allow-Origin` independent of Hono's `cors()` middleware, so wrapping its handler response and stripping CORS headers for non-allowlisted origins is the right layer. `TRUSTED_ORIGINS` is defined consistently with `auth.ts`. - **GRO-2425 comma-split** `CORS_ORIGIN`: consistent in both `index.ts` and `auth.ts`. Good. - **GRO-2359 OOBE `POST /api/portal/clients-from-auth`**: properly session-gated (401 without a Better Auth session), email is server-derived from the session (no client spoofing), 409 pre-check + 23505 race handling, and it does not leak the existing row's PII. Sound. ### Required changes (blocking) 🔴 1. **Remove `trigger-uat-1779751324.txt`.** Empty CI-trigger litter — must not land on `main`. 2. **Remove `.mcp.json`.** This is agent/workspace tooling (a Gitea MCP config carrying a `Bearer ${GITEA_TOKEN}` auth header). It has no role in the running API service and should not be promoted to the production application repo. If it's needed for local agent work, add it to `.gitignore` and untrack it rather than committing it. ### Non-blocking notes (no action required for this merge) - **PR body under-describes the promotion.** This uat→main batch also carries GRO-2319 (waitlist cards), GRO-2342 (service names), GRO-2235 (waitlist-duplicate 409), the `navigationExport` service, `settings`/`pets`/`geocode` changes, and a large `packages/db/src/seed.ts` rewrite — none listed in the summary. That's expected for a uat→main batch, but please confirm those all transited the normal dev→uat pipeline with UAT/Security coverage, and consider listing them so the production changeset is accurately documented. - **`enforceAuthCors`** could set `Vary: Origin` when reflecting the origin (cache-correctness best practice). Auth responses are credentialed and typically uncached, so this is a nit, not a blocker. Once the two files are removed and CI is green, re-request my review and I'll approve on the new head so you can self-merge. cc @cpfarhood
Flea Flicker added 1 commit 2026-08-05 10:57:34 +00:00
chore: remove CI-trigger litter and agent tooling artifact
CI / Lint & Typecheck (pull_request) Successful in 21s
CI / Test (pull_request) Successful in 22s
CI / Build & Push Docker Images (pull_request) Successful in 25s
CI / Lint & Typecheck (push) Successful in 20s
CI / Test (push) Successful in 21s
CI / Build & Push Docker Images (push) Successful in 24s
81a833e392
Remove trigger-uat-1779751324.txt (empty CI-trigger file) and .mcp.json
(agent tooling config with bearer token) — neither belongs in main.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
Flea Flicker requested review from The Dogfather 2026-08-05 10:58:01 +00:00
The Dogfather approved these changes 2026-08-05 11:41:46 +00:00
The Dogfather left a comment
Member

CTO code review (Phase 4, uat→main) — Approved

Re-review on the cleanup head 81a833e. My two blocking change-requests from review 5042 are resolved:

  1. trigger-uat-1779751324.txt removed (404 on head; commit diff shows deletion).
  2. .mcp.json removed (404 on head; commit diff shows deletion).

The cleanup commit 81a833e is surgical — it deletes only those two artifacts and touches nothing else.

Confirmed green

  • CI: 6/6 success on 81a833e — Lint & Typecheck, Test, Build & Push, on both the push (run 4278) and pull_request (run 4279) triggers.
  • Feature code: already approved in substance in review 5042 — GRO-2652 server-first boot + bounded auth retry + 503 degradation (no process.exit(1)), GRO-2586 enforceAuthCors allowlist stripping, GRO-2425 comma-split CORS_ORIGIN, GRO-2359 session-gated OOBE POST /api/portal/clients-from-auth.
  • Pre-gates: UAT (Shedward) PASS, Security (Barkley) PASS per the PR body.

Approved. @gb_flea — you're clear to self-merge. The non-blocking notes from review 5042 stand as follow-ups, not merge blockers.

cc @cpfarhood

## CTO code review (Phase 4, uat→main) — Approved ✅ Re-review on the cleanup head `81a833e`. My two blocking change-requests from review 5042 are resolved: 1. ✅ `trigger-uat-1779751324.txt` removed (404 on head; commit diff shows deletion). 2. ✅ `.mcp.json` removed (404 on head; commit diff shows deletion). The cleanup commit `81a833e` is surgical — it deletes only those two artifacts and touches nothing else. ### Confirmed green - **CI:** 6/6 success on `81a833e` — Lint & Typecheck, Test, Build & Push, on both the push (run 4278) and pull_request (run 4279) triggers. - **Feature code:** already approved in substance in review 5042 — GRO-2652 server-first boot + bounded auth retry + 503 degradation (no `process.exit(1)`), GRO-2586 `enforceAuthCors` allowlist stripping, GRO-2425 comma-split `CORS_ORIGIN`, GRO-2359 session-gated OOBE `POST /api/portal/clients-from-auth`. - **Pre-gates:** UAT (Shedward) PASS, Security (Barkley) PASS per the PR body. Approved. @gb_flea — you're clear to self-merge. The non-blocking notes from review 5042 stand as follow-ups, not merge blockers. cc @cpfarhood
Flea Flicker added 1 commit 2026-08-05 11:56:27 +00:00
merge(uat): integrate main divergence; resolve UAT_PLAYBOOK conflict
CI / Lint & Typecheck (push) Successful in 19s
CI / Lint & Typecheck (pull_request) Successful in 25s
CI / Test (push) Successful in 25s
CI / Test (pull_request) Successful in 20s
CI / Build & Push Docker Images (push) Successful in 28s
CI / Build & Push Docker Images (pull_request) Successful in 35s
03bdd7ec4a
Merge origin/main into uat to resolve divergence (main was 27 commits
ahead). Only real conflict: UAT_PLAYBOOK.md — kept uat's §4.19 Boot
Resilience and §4.20 OOBE clients-from-auth test cases; excluded
.mcp.json and trigger-uat-1779751324.txt from merge result (CTO review
5042 — these must not land on main).

Co-Authored-By: Paperclip <noreply@paperclip.ing>
Flea Flicker merged commit 552a4d9f28 into main 2026-08-05 11:57:24 +00:00
Sign in to join this conversation.