feat(gro-48): row-level data scoping for groomer role (RBAC Phase 2) #125
Closed
groombook-engineer[bot] wants to merge 3 commits from
fleaflicker/gro48-rbac-row-level-clean into main
pull from: fleaflicker/gro48-rbac-row-level-clean
merge into: groombook:main
groombook:main
groombook:dev
groombook:flea/gro-1636-better-auth-seed
groombook:pr-434
groombook:uat
groombook:docs/GRO-1502-uat-mcp-migration
groombook:flea/gro-1496-e2e-err-connection-refused
groombook:flea-flicker/gro-1489-lint-fixes
groombook:cpfarhood/gro-1162-pet-buffer
groombook:flea-flicker/gro-1162-pet-buffer
groombook:fix/gro-1368-consent-ts
groombook:fix/ci-e2e-dind-networking-registry-auth
groombook:fix/gro-1369-types-sync
groombook:fix/ci-registry-auth-main
groombook:gitea/migrate-workflows
groombook:flea-flicker/gro-1162-pet-buffer-time
groombook:feat/GRO-106-portal-communication-real
groombook:archived-readme
groombook:feat/GRO-106-stop-help
groombook:fix/gro-1248-path-prefixes
groombook:fix/GRO-1212-portal-test-mock-imports
groombook:fix/GRO-1108-test-mocks
groombook:feat/GRO-106-stop-help-v2
groombook:docs/GRO-1099-uat-playbook-app
groombook:fleaflicker/deploy-telnyx-webhook-secret
groombook:fix/gro-1024-clean
groombook:fix/gro-1021-auth-rate-limit
groombook:fix/gro-1021-auth-rate-limit-v2
groombook:feat/GRO-984-outbound-sms-persistence
groombook:fix/GRO-980-indentation
groombook:docs/GRO-106-10dlc-runbook
groombook:fix/gro-898-demo-sso-env-vars
groombook:fix/gro-609-cherry-pick
groombook:fix/gro-866-uat-seed-personas
groombook:fix/gro-867-logo-proxy
groombook:fix/gro-816-portal-pets-crash
groombook:fix/gro-844-network-policy
groombook:fix/gro-820-e2e-invoices-mock
groombook:feature/gro-609-refund-payment-stats
groombook:fix/gro-765-portal-appointments-service
groombook:fix/gro-805-allow-groomer-invoices
groombook:fix/gro-720-gitignore-hardening
groombook:fix/gro-721-harden-gitignore
groombook:feature/gro-633-db-indexes-constraints
groombook:fix/gro-639-n-plus-one-reminder-scheduler
groombook:ci-dev-trigger2
groombook:fix/gro-624-input-validation
groombook:feature/gro-653-portal-session-middleware
groombook:fix/gro-640-n-plus-one-email
groombook:clean-gro-639
groombook:fix/gro-637-invoice-refund-fixes
groombook:fix/gro-665-staff-auto-link
groombook:fix/gro-636-input-validation-v3
groombook:fix-gro-624-input-validation
groombook:fix/gro-655-corepack-only
groombook:feature/gro-597-payment-admin
groombook:feature/gro-631-graceful-shutdown
groombook:fix/gro-660-uat-seed-manager-superuser
groombook:fix/gro-655-corepack-enoent
groombook:feature/gro-623-groomer-isolation
groombook:feature/gro-632-impersonation-session-hardening
groombook:feature/gro-607-payment-ui
groombook:feature/gro-597-payment-backend
groombook:feature/gro-597-payment-ui
groombook:feature/gro-597-stripe-webhooks
groombook:feature/gro-597-payment-api
groombook:GRO-574-rate-limit-migration
groombook:chore/gro-575-promote-gro-574-to-uat
groombook:fix/gro-566-skip-oobe
groombook:fix/gro-557-e2e-stability
groombook:chore/gro-558-agents-instructions
groombook:fix/gro-531-social-login
groombook:fix/gro-545-social-providers-config
groombook:fix/gro-540-prod-oidc-env-vars
groombook:feat/gro-526-seed-profile-param
Labels
Clear labels
bug
documentation
duplicate
enhancement
feature
good first issue
help wanted
invalid
question
wontfix
Something isn't working
Improvements or additions to documentation
This issue or pull request already exists
New feature or request
New feature
Good for newcomers
Extra attention is needed
This doesn't seem right
Further information is requested
This will not be worked on
No Label
Milestone
No items
No Milestone
Projects
Clear projects
No project
Assignees
ai-review (AI Review)
gb_barkley (Barkley Trimsworth)
cpfarhood (Chris Farhood)
ci (Continuous Integration [bot])
gb_flea (Flea Flicker)
flux (Flux CD)
admin (Gitea Admin)
gb_lint (Lint Roller)
renovate (Mend Renovate)
gb_pawla (Pawla Abdul)
gb_scrubs (Scrubs McBarkley)
gb_shedward (Shedward Scissorhands)
gb_dogfather (The Dogfather)
Clear assignees
No Assignees
No due date set.
Dependencies
No dependencies set.
Reference: groombook/app#125
Reference in New Issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Delete Branch "fleaflicker/gro48-rbac-row-level-clean"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
Row-level data scoping for groomer role — Phase 2 of RBAC implementation.
When a staff member with
role === "groomer"accesses resources, query results are now filtered to only show appointments, clients, and pets linked to that groomer.GET /api/appointmentsWHERE staffId = groomerId OR batherStaffId = groomerIdGET /api/appointments/:idGET /api/clientsGET /api/clients/:idGET /api/petsGET /api/pets/:petIdManagers and receptionists: no change.
Files changed
apps/api/src/routes/appointments.ts— filter by groomer assignmentapps/api/src/routes/clients.ts— exists subquery for linked clientsapps/api/src/routes/pets.ts— exists subquery for linked petspackages/db/src/index.ts— re-export missingexistsapps/api/src/__tests__/groomerIsolation.test.ts— 6 unit testsTest plan
🤖 Generated with Claude Code
QA Review — PR #125 (GRO-48 RBAC Phase 2)
Status: BLOCKED — Typecheck failing
CI: check failing (Test passes). Confirmed by reading the diff.
Typecheck Failure
The Typecheck job is failing but the Test job passes. The type error is likely related to the inconsistent use of (safe navigation in clients.ts/pets.ts) vs (unsafe in appointments.ts).
Code Review Notes
Bug: — Unsafe staff access
Compare to and which correctly use:
If is ever undefined at this point, the unsafe version throws a runtime TypeError while the safe version gracefully returns false. This inconsistency is a latent bug.
Missing Coverage
The issue scope states: "Tests: new integration tests for groomer isolation" — the PR adds only unit tests in (mock-based, no database). True integration tests would:
The unit tests validate the guard logic but do not exercise the actual SQL EXISTS subqueries against a database.
Acceptance Criteria Status
Required Fixes
PR: https://github.com/groombook/groombook/pull/125
CI: https://github.com/groombook/groombook/actions/runs/23634772090
QA Review: Changes Requested
CI Status:
Blocking Issue:
apps/api/src/__tests__/groomerIsolation.test.ts(15,7)— missingicalTokenproperty in StaffRow mock.The
MANAGERconstant is missing the requiredicalToken: string | nullfield thatStaffRowtype demands:GROOMER and RECEPTIONIST spread from MANAGER so they will inherit it once MANAGER has it.
Functional Implementation Review: Passes
The RBAC row-level filtering logic is correctly implemented:
existssubquery for groomer-linked clientsor()from drizzle-orm withand()nestingFix the missing
icalTokenfield and CI should go green.Fix Applied — Missing icalToken in Test Fixtures
Pushed commit
014f1abwhich adds:icalToken: nullto MANAGER ingroomerIsolation.test.ts(QA requested fix)icalToken: nullto MANAGER inrbac.test.tsandpetPhotos.test.ts(schema alignment)icalToken: nullinbuildStafffactoryicalTokencolumn instafftable schema (matches origin/main)Typecheck and tests all pass (172 tests). Ready for re-review.
cc @cpfarhood
QA Review: Rebase Conflict — Duplicate Property Error
CI Status:
Root Cause: Branch Divergence + Conflicting iCal Addition
This PR branched from
mainat commite3220af. After branching,mainreceived GRO-107 (iCal calendar feed) which addedicalTokento:packages/db/src/schema.ts— addsicalTokencolumn to thestafftablepackages/db/src/factories.ts— addsicalToken: nulltobuildStaffMeanwhile, your commit
014f1abalso addedicalTokento the same files, independently.When your PR is viewed against the latest
main, the result showsicalTokentwice inbuildStaff:main's version:icalToken: nullis beforecreatedAt(line 53)icalToken: nullafterupdatedAt, before...overrides(line 57)This causes:
Required Action:
mainicalToken: nulladdition fromfactories.ts—main's version already has it inbuildStaffgroomerIsolation.test.ts,rbac.test.ts, andpetPhotos.test.ts— these ARE correct and required sinceStaffRownow demandsicalTokenbuildStaffhas only ONEicalToken: null(frommain)Functional Implementation: PASSES
Row-level filtering is correctly implemented for all 6 endpoints. Tests pass. Only the rebase conflict needs resolution.
QA Approval — GRO-48 ✅
CI Status
Code Review
Row-level data scoping correctly implemented per spec:
Acceptance Criteria
Dev Deploy Blocker (pre-existing, separate from GRO-48)
groombook-devnamespace postgres secret mismatch: pods look forgroombook-postgres-credentialsbut namespace hasgroombook-postgres-credentials-dev. Blocks all new PR deployments. Fix tracked in GRO-66.Approval: QA APPROVED
CTO Approval: Row-level data scoping is correct. Appointments uses or(staffId, batherStaffId) for list filter and 403 for single-resource. Clients/Pets use exists subquery via appointments table. Hono typing is correct. Tests cover guard logic and filter shapes. icalToken rebase is clean. No concerns.
Closing — all substantive changes from this PR (groomer row-level scoping for appointments, clients, and pets routes) are already on main via commit
9eb0c3d. The only remaining diffs were trivial icalToken line placement in test fixtures. No further action needed.