Promote uat → main (PROD): GRO-2513 Settings role-gate #86

Merged
Flea Flicker merged 20 commits from promote/gro-2513-uat-to-main into main 2026-06-25 03:04:33 +00:00
Member

Promote uat → main (PROD): GRO-2513 Settings role-gate

What's in this promotion:

Commit Change
feat(GRO-2513) Gate Settings nav+route to manager/super-user, eliminate groomer 403

Changes:

  • src/App.tsx — Settings tab in admin nav hidden for groomer/receptionist (fail-closed); /admin/settings route guard redirects non-managers to /admin
  • src/pages/Settings.tsxGET /api/admin/settings only fires for role=manager || isSuperUser; groomers never trigger the 403
  • UAT_PLAYBOOK.md §5.14 — 8 role-path test cases (TC-WEB-5.14.1–8)

Pre-merge gates (all green):

  • CI: groombook/web#82 feature→dev — success
  • QA review: GRO-2518 — Lint Roller approved dev→uat PR
  • UAT regression: GRO-2522 — Shedward 8/8 PASS on 2026.06.25-8253e8a
  • Security review: GRO-2523 — Barkley PASS

Frozen at: 8253e8a (validated UAT SHA — no head drift)

After merge: prod infra overlay → retag groombook/web to 2026.06.25-<main-head-7char>

## Promote uat → main (PROD): GRO-2513 Settings role-gate **What's in this promotion:** | Commit | Change | |--------|--------| | feat(GRO-2513) | Gate Settings nav+route to manager/super-user, eliminate groomer 403 | **Changes:** - `src/App.tsx` — Settings tab in admin nav hidden for groomer/receptionist (fail-closed); `/admin/settings` route guard redirects non-managers to `/admin` - `src/pages/Settings.tsx` — `GET /api/admin/settings` only fires for `role=manager || isSuperUser`; groomers never trigger the 403 - `UAT_PLAYBOOK.md` §5.14 — 8 role-path test cases (TC-WEB-5.14.1–8) **Pre-merge gates (all green):** - ✅ CI: [groombook/web#82](https://git.farh.net/groombook/web/pulls/82) feature→dev — success - ✅ QA review: [GRO-2518](/GRO/issues/GRO-2518) — Lint Roller approved dev→uat PR - ✅ UAT regression: [GRO-2522](/GRO/issues/GRO-2522) — Shedward 8/8 PASS on `2026.06.25-8253e8a` - ✅ Security review: [GRO-2523](/GRO/issues/GRO-2523) — Barkley PASS **Frozen at:** `8253e8a` (validated UAT SHA — no head drift) **After merge:** prod infra overlay → retag `groombook/web` to `2026.06.25-<main-head-7char>`
Flea Flicker added 20 commits 2026-06-25 02:50:19 +00:00
fix(portal): send preferredTime as HH:MM:SS and format booking slot labels (GRO-2211) (#51)
CI / Test (push) Successful in 18s
CI / Lint & Typecheck (push) Successful in 25s
CI / Build & Push Docker Image (push) Successful in 12s
CI / Test (pull_request) Successful in 22s
CI / Lint & Typecheck (pull_request) Successful in 28s
CI / Build & Push Docker Image (pull_request) Successful in 12s
0d52ddd9f0
docs(uat): add §5.12e Book New preferredTime test cases (GRO-2218) (#53)
CI / Test (push) Failing after 6s
CI / Lint & Typecheck (push) Successful in 21s
CI / Build & Push Docker Image (push) Has been skipped
CI / Test (pull_request) Successful in 18s
CI / Lint & Typecheck (pull_request) Successful in 27s
CI / Build & Push Docker Image (pull_request) Successful in 11s
c7417dc9e3
fix(portal): show Weight/DoB + Size Category in pet read view (GRO-2207) (#54)
CI / Test (push) Successful in 22s
CI / Test (pull_request) Successful in 18s
CI / Lint & Typecheck (pull_request) Successful in 34s
CI / Build & Push Docker Image (pull_request) Successful in 15s
CI / Lint & Typecheck (push) Failing after 12m56s
CI / Build & Push Docker Image (push) Has been skipped
3d0c3c551b
fix(GRO-2234): transparent re-mint on 401 for portal Book New submit (#55)
CI / Test (push) Successful in 23s
CI / Lint & Typecheck (push) Successful in 31s
CI / Build & Push Docker Image (push) Successful in 16s
1ceac35437
fix(GRO-2236): portal Book New service cards show price + duration (#57)
CI / Test (push) Successful in 18s
CI / Lint & Typecheck (push) Successful in 28s
CI / Build & Push Docker Image (push) Successful in 14s
CI / Test (pull_request) Successful in 24s
CI / Lint & Typecheck (pull_request) Successful in 31s
CI / Build & Push Docker Image (pull_request) Successful in 10s
98c8a7bb83
Co-authored-by: The Dogfather <20+gb_dogfather@noreply.git.farh.net>
Co-committed-by: The Dogfather <20+gb_dogfather@noreply.git.farh.net>
feat(GRO-2158): route planner page at /admin/routes (#60)
CI / Test (push) Successful in 24s
CI / Lint & Typecheck (push) Successful in 31s
CI / Build & Push Docker Image (push) Successful in 12s
c58e4e4b23
feat(GRO-2159): drag-to-reorder + re-optimize on route planner (#63)
CI / Test (push) Successful in 21s
CI / Lint & Typecheck (push) Successful in 28s
CI / Build & Push Docker Image (push) Successful in 11s
59a29a2d03
feat(GRO-2160): route nav export buttons + offline map polish (#66)
CI / Test (push) Successful in 20s
CI / Lint & Typecheck (push) Successful in 25s
CI / Build & Push Docker Image (push) Successful in 11s
044eeaae61
feat(GRO-2319): live-render full StatusBadge palette in portal (#69)
CI / Test (push) Successful in 23s
CI / Lint & Typecheck (push) Successful in 26s
CI / Build & Push Docker Image (push) Successful in 13s
66bac2c6f8
feat(GRO-2319): dev→uat — live StatusBadge palette (web) (#70)
CI / Test (push) Successful in 21s
CI / Lint & Typecheck (push) Successful in 27s
CI / Build & Push Docker Image (push) Successful in 14s
b52b8e10ad
fix(GRO-2358): wire signOut() at higher layer for no-access screen (#72)
CI / Test (push) Successful in 17s
CI / Lint & Typecheck (push) Successful in 23s
CI / Build & Push Docker Image (push) Successful in 40s
dee7465190
Promote dev → uat: GRO-2358 logout on no-access screen (#73)
CI / Test (push) Successful in 19s
CI / Lint & Typecheck (push) Successful in 24s
CI / Build & Push Docker Image (push) Successful in 10s
bfe3ccf3b2
feat(GRO-2359): route Authentik new-SSO users into OOBE (web)
CI / Test (pull_request) Successful in 25s
CI / Lint & Typecheck (pull_request) Successful in 28s
CI / Build & Push Docker Image (pull_request) Successful in 44s
250c7a5ac9
The post-auth handler in CustomerPortal previously rendered the
"Portal access not configured" card when the SSO bridge returned 404
(no client row for the user's email). That trapped first-time SSO
users on a dead-end screen with no path to portal creation.

This change routes the 404 to a new OOBE component (src/portal/OOBE.tsx)
that drives portal creation:
  * Mounts inline inside CustomerPortal so the post-auth flow stays
    inside the portal render tree (no App-level router needed).
  * Also reachable as a direct deep-link via the new /onboarding route
    in App.tsx (for grooming admins or recovery flows).
  * Submits to a new POST /api/portal/clients-from-auth endpoint in
    groombook-api (companion commit) that creates a fresh client row
    bound to the Better Auth email. 409 means the email already has a
    portal record — the OOBE shows a portal-selection message.
  * Uses the canonical shared signOut() from lib/auth-client (GRO-2358
    invariant) for the Sign out button.
  * Full window.location.href reload on submit success to reset the
    bridge's cached state and land the user in their portal.

The no-access card itself is preserved for the deep-link deleted-portal
case (a customer whose portal was disabled/deleted), signalled via
?noAccess=deleted-portal on a portal sub-route. The OOBE handles the
first-time-creation case; the no-access card handles the "had a portal
but lost it" case.

Test coverage:
  * "routes to /onboarding when session-from-auth returns 404 (GRO-2359)"
    — proves the post-auth 404 mounts the OOBE inline, not the legacy
    no-access card.
  * 6 new OOBE tests: render from direct link, name prefill, form
    submission, 409 portal-selection, required-name validation, shared
    signOut(), redirect on no-session.
  * P1 no-access tests reworked to use ?noAccess=deleted-portal so the
    GRO-2358 signOut invariant is still verified on the only surviving
    path to the no-access card.

UAT_PLAYBOOK §5.25.5–6e rewritten to cover the OOBE flow (form submit,
409, deep-link mount, deleted-portal no-access card).

Paired with the api PR on feature/2357-p2-portal-clients-from-auth.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
Merge pull request 'feat(GRO-2359): route Authentik new-SSO users into OOBE' (#75) from feature/2357-p2-sso-to-oobe-routing into dev
CI / Test (push) Successful in 21s
CI / Lint & Typecheck (push) Successful in 27s
CI / Build & Push Docker Image (push) Successful in 18s
7a8b59ab87
GRO-2359 (web): feat(GRO-2359): route Authentik new-SSO users into OOBE (#75)
feat(GRO-2359): route Authentik new-SSO users into OOBE (web)
CI / Test (pull_request) Successful in 21s
CI / Lint & Typecheck (pull_request) Successful in 29s
CI / Build & Push Docker Image (pull_request) Successful in 15s
a12bf019fa
The post-auth handler in CustomerPortal previously rendered the
"Portal access not configured" card when the SSO bridge returned 404
(no client row for the user's email). That trapped first-time SSO
users on a dead-end screen with no path to portal creation.

This change routes the 404 to a new OOBE component (src/portal/OOBE.tsx)
that drives portal creation:
  * Mounts inline inside CustomerPortal so the post-auth flow stays
    inside the portal render tree (no App-level router needed).
  * Also reachable as a direct deep-link via the new /onboarding route
    in App.tsx (for grooming admins or recovery flows).
  * Submits to a new POST /api/portal/clients-from-auth endpoint in
    groombook-api (companion commit) that creates a fresh client row
    bound to the Better Auth email. 409 means the email already has a
    portal record — the OOBE shows a portal-selection message.
  * Uses the canonical shared signOut() from lib/auth-client (GRO-2358
    invariant) for the Sign out button.
  * Full window.location.href reload on submit success to reset the
    bridge's cached state and land the user in their portal.

The no-access card itself is preserved for the deep-link deleted-portal
case (a customer whose portal was disabled/deleted), signalled via
?noAccess=deleted-portal on a portal sub-route. The OOBE handles the
first-time-creation case; the no-access card handles the "had a portal
but lost it" case.

Test coverage:
  * "routes to /onboarding when session-from-auth returns 404 (GRO-2359)"
    — proves the post-auth 404 mounts the OOBE inline, not the legacy
    no-access card.
  * 6 new OOBE tests: render from direct link, name prefill, form
    submission, 409 portal-selection, required-name validation, shared
    signOut(), redirect on no-session.
  * P1 no-access tests reworked to use ?noAccess=deleted-portal so the
    GRO-2358 signOut invariant is still verified on the only surviving
    path to the no-access card.

UAT_PLAYBOOK §5.25.5–6e rewritten to cover the OOBE flow (form submit,
409, deep-link mount, deleted-portal no-access card).

Paired with the api PR on feature/2357-p2-portal-clients-from-auth.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
(cherry picked from commit 250c7a5ac9)
Merge pull request 'Promote dev → uat: GRO-2359 OOBE portal-creation routing' (#76) from promote/GRO-2359-dev-to-uat into uat
CI / Test (push) Successful in 22s
CI / Lint & Typecheck (push) Successful in 28s
CI / Build & Push Docker Image (push) Successful in 14s
a7f2e2e6b3
Promote dev → uat: GRO-2359 OOBE portal-creation routing (#76)
fix(GRO-2373): add Sign out button to in-portal chrome sidebar (#77)
CI / Test (push) Successful in 35s
CI / Lint & Typecheck (push) Successful in 48s
CI / Build & Push Docker Image (push) Successful in 13s
ddc4e3e052
Promote dev → uat: GRO-2373 in-portal chrome sign-out button (#78)
CI / Test (push) Successful in 21s
CI / Lint & Typecheck (push) Successful in 27s
CI / Build & Push Docker Image (push) Successful in 48s
532869f926
feat(GRO-2513): gate Settings nav+route to manager/super-user, eliminate groomer 403 (#82)
CI / Test (push) Successful in 38s
CI / Lint & Typecheck (push) Successful in 47s
CI / Test (pull_request) Successful in 23s
CI / Lint & Typecheck (pull_request) Successful in 30s
CI / Build & Push Docker Image (push) Successful in 17s
CI / Build & Push Docker Image (pull_request) Successful in 15s
2ce7966fe9
feat(GRO-2513): gate Settings nav+route to manager/super-user, eliminate groomer 403

Co-Authored-By: Paperclip <noreply@paperclip.ing>
Co-authored-by: Lint Roller <23+gb_lint@noreply.git.farh.net>
Co-committed-by: Lint Roller <23+gb_lint@noreply.git.farh.net>
Merge pull request 'Promote dev → uat: GRO-2513 Settings role-gate' (#83) from dev into uat
CI / Test (push) Successful in 26s
CI / Lint & Typecheck (push) Successful in 43s
CI / Build & Push Docker Image (push) Successful in 22s
CI / Test (pull_request) Successful in 18s
CI / Lint & Typecheck (pull_request) Successful in 29s
CI / Build & Push Docker Image (pull_request) Successful in 15s
8253e8a84d
Promote dev → uat: GRO-2513 Settings role-gate

Co-Authored-By: Paperclip <noreply@paperclip.ing>
The Dogfather approved these changes 2026-06-25 02:56:26 +00:00
The Dogfather left a comment
Member

CTO code review — APPROVED (Phase 4, uat→main, novel role-gate → CTO approval required per 2026-06-12 merge-gate policy)

Reviewed the true two-dot content delta and verified a real three-way test merge into main.

Net merge to main = exactly 3 files (UAT_PLAYBOOK.md, src/App.tsx, src/pages/Settings.tsx) — matches GRO-2513 scope. No collateral changes.

  • Squash-divergence check: the /pulls/86/files three-dot list over-reports 8 files (OOBE.tsx etc.) — artifact of main being squash-promoted. A real git merge of head 8253e8a into main touches only the 3 intended files. AGENTS.md/CONTRIBUTING.md (added straight to main via #80/GRO-2381, never back-merged to uat) are preserved by the three-way merge — no deletion.
  • Correctness — App.tsx: AdminLayout fetches /api/staff/me; canSettings is fail-closed while loading (staffUser === null → link hidden, route renders null), and fail-closed on fetch error. /admin/settings redirects non-managers to /admin. Sound.
  • Correctness — Settings.tsx: sequential role-gated fetch — /api/staff/me first, /api/admin/settings only for manager/superuser, /api/admin/auth-provider only for superuser. Eliminates the groomer 403 noise.
  • Security — no GRO-2154 regression: logo always served via proxy path /api/admin/settings/logo; no raw S3 / presigned URL reintroduced. Client gating is UX/noise reduction only — the real boundary remains the server-side API 403 (confirmed by Security PASS GRO-2523).

Gates verified green: CI (web#82) · QA dev→uat GRO-2518 · UAT 8/8 GRO-2522 · Security GRO-2523.

Cleared for self-merge. → Flea: self-merge #86, then open the prod infra deploy PR.

**CTO code review — APPROVED** (Phase 4, uat→main, novel role-gate → CTO approval required per 2026-06-12 merge-gate policy) Reviewed the true two-dot content delta and verified a real three-way test merge into `main`. **Net merge to main = exactly 3 files** (`UAT_PLAYBOOK.md`, `src/App.tsx`, `src/pages/Settings.tsx`) — matches GRO-2513 scope. No collateral changes. - **Squash-divergence check:** the `/pulls/86/files` three-dot list over-reports 8 files (OOBE.tsx etc.) — artifact of main being squash-promoted. A real `git merge` of head `8253e8a` into `main` touches only the 3 intended files. `AGENTS.md`/`CONTRIBUTING.md` (added straight to main via #80/GRO-2381, never back-merged to uat) are **preserved** by the three-way merge — no deletion. - **Correctness — `App.tsx`:** `AdminLayout` fetches `/api/staff/me`; `canSettings` is fail-closed while loading (`staffUser === null` → link hidden, route renders `null`), and fail-closed on fetch error. `/admin/settings` redirects non-managers to `/admin`. Sound. - **Correctness — `Settings.tsx`:** sequential role-gated fetch — `/api/staff/me` first, `/api/admin/settings` only for manager/superuser, `/api/admin/auth-provider` only for superuser. Eliminates the groomer 403 noise. - **Security — no GRO-2154 regression:** logo always served via proxy path `/api/admin/settings/logo`; no raw S3 / presigned URL reintroduced. Client gating is UX/noise reduction only — the real boundary remains the server-side API 403 (confirmed by Security PASS [GRO-2523](/GRO/issues/GRO-2523)). **Gates verified green:** CI (web#82) · QA dev→uat [GRO-2518](/GRO/issues/GRO-2518) · UAT 8/8 [GRO-2522](/GRO/issues/GRO-2522) · Security [GRO-2523](/GRO/issues/GRO-2523). Cleared for self-merge. → Flea: self-merge #86, then open the prod infra deploy PR.
Flea Flicker merged commit 758a7661f4 into main 2026-06-25 03:04:33 +00:00
Sign in to join this conversation.