Skip to content

feat(store): domain, group, role, and service user reads skip soft-deleted rows - #1938

Merged
whoAbhishekSah merged 1 commit into
mainfrom
soft-delete-live-reads-tenancy
Sep 23, 2026
Merged

whoAbhishekSah merged 1 commit into
mainfrom
soft-delete-live-reads-tenancy

Conversation

@whoAbhishekSah

Copy link
Copy Markdown
Member

Last of three PRs that make the identity repositories skip soft-deleted rows. Follows #1931 and #1936.

What

  • Every read in the domain, group, role, service user, and service user credential repositories goes through fromLive, or adds live("s") where the query aliases the table.
  • Updates keyed by id add the live filter. The policy subquery in ListMissingOrgPolicy only counts live policies.
  • The organization title subqueries in the service user create and delete paths stay on the plain From. They only attribute the audit record.
  • The role ON CONFLICT upsert is unchanged. It moves with the unique constraint in a later PR.

Behaviour change

None today. No row has deleted_at set yet.

Tested

Domains and service users had no repository suite, so this adds one for each. Groups and roles get a case in their existing suites. Each test soft-deletes a row and checks every get, list, and update skips it, comparing counts before and after. Store package passes with -race.

…leted rows

Every read in the domain, group, role, service user, and service user
credential repositories goes through fromLive, or adds the live filter where
the query aliases the table. Updates keyed by id add the filter too. The
policy subquery in ListMissingOrgPolicy only counts live policies.

The organization title subqueries in the service user create and delete paths
stay on the plain From. They only attribute the audit record, and the trail
must stay attributed after the organization is deleted.

Domains and service users had no repository test suite, so this adds one for
each. Groups and roles get a case in their existing suites.
@vercel

vercel Bot commented Sep 22, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
frontier Ready Ready Preview Sep 22, 2026 9:46am UTC

@coderabbitai

coderabbitai Bot commented Sep 22, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

Navigate logical layers of code changes, visualize relationships, and explore their blast radius.

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository: raystack/frontier/.coderabbit.yaml

Review profile: CHILL

Plan: Advanced

Run ID: 0078abc9-c381-4f8d-bf5f-211f97a36077

📥 Commits

Reviewing files that changed from the base of the PR and between 64aa400 and 9e7797a.

📒 Files selected for processing (9)
  • internal/store/postgres/domain_repository.go
  • internal/store/postgres/domain_repository_test.go
  • internal/store/postgres/group_repository.go
  • internal/store/postgres/group_repository_test.go
  • internal/store/postgres/role_repository.go
  • internal/store/postgres/role_repository_test.go
  • internal/store/postgres/serviceuser_credential_repository.go
  • internal/store/postgres/serviceuser_repository.go
  • internal/store/postgres/serviceuser_repository_test.go

Included review availability: Your plan provides up to 2 included reviews per hour; 1 remains after this review.


📝 Summary

Summary by CodeRabbit

  • Bug Fixes

    • Soft-deleted domains, groups, roles, service users, and credentials are now excluded from searches and listings.
    • Updates and state changes no longer affect soft-deleted records.
    • Missing-record errors are returned consistently when attempting to access or modify deleted records.
  • Tests

    • Added coverage verifying soft-deleted records remain inaccessible across repository operations.

Walkthrough

Changes

Repository reads and mutations now exclude soft-deleted domains, groups, roles, service users, credentials, and policies. Integration tests verify the behavior across these repositories.

Soft-delete repository filtering

Layer / File(s) Summary
Domain live-row filtering
internal/store/postgres/domain_repository.go, internal/store/postgres/domain_repository_test.go
Domain List, Get, and Update operations now require live rows. Tests verify deleted domains are omitted or return domain.ErrNotExist.
Group and role live-row filtering
internal/store/postgres/group_repository.go, internal/store/postgres/group_repository_test.go, internal/store/postgres/role_repository.go, internal/store/postgres/role_repository_test.go
Group and role reads and mutations now require live rows. Tests verify deleted records are excluded and treated as nonexistent.
Service user and credential filtering
internal/store/postgres/serviceuser_repository.go, internal/store/postgres/serviceuser_credential_repository.go, internal/store/postgres/serviceuser_repository_test.go
Service user, credential, and policy-candidate queries now filter live records. Docker-backed tests verify deleted records are excluded.

Priority: ⬇️ Low

Estimated code review effort: 3 (Moderate) | ~25 minutes

Change: Bug fix

Suggested reviewers: amangit07

Merge Risk: ⚪ Minimal · up to 9e779

The change makes soft-deleted records invisible to repository reads and mutations while preserving live-record behavior; the supplied coverage shows the intended not-found and filtering behavior, so it is ready to merge.

🚥 Pre-merge checks | ✅ 2
✅ Passed checks (2 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coveralls

Copy link
Copy Markdown

Coverage Report for CI Build 35712212722

Coverage increased (+0.8%) to 51.546%

Details

  • Coverage increased (+0.8%) from the base build.
  • Patch coverage: 3 uncovered changes across 2 files (15 of 18 lines covered, 83.33%).
  • No coverage regressions found.

Uncovered Changes

File Changed Covered %
internal/store/postgres/serviceuser_repository.go 5 3 60.0%
internal/store/postgres/role_repository.go 3 2 66.67%
Total (5 files) 18 15 83.33%

Coverage Regressions

No coverage regressions found.


Coverage Stats

Coverage Status
Relevant Lines: 41111
Covered Lines: 21191
Line Coverage: 51.55%
Coverage Strength: 16.74 hits per line

💛 - Coveralls

@whoAbhishekSah

Copy link
Copy Markdown
Member Author

Tested live on a local server built from this branch (main with #1931 and #1936 merged). Fixtures: a verified domain on a live org, two groups with members, a custom org role bound to a user, two service users with Basic credentials and a minted access token, a service user project policy, and a throwaway org with its own service user. Ran every affected RPC before and after setting deleted_at directly in the DB, then exercised the write paths.

Path RPCs Result
Domain reads GetOrganizationDomain, ListOrganizationDomains, joinable orgs in ListOrganizationsByCurrentUser hidden domain dropped
Domain join JoinOrganization with the verified domain hidden refused
Group reads GetGroup, ListOrganizationGroups, ListCurrentUserGroups, ListGroupUsers, ListProjectGroups not found / dropped
Role reads GetOrganizationRole, ListOrganizationRoles, roles in ListProjectUsers not found / hidden role dropped
Service user reads GetServiceUser, ListServiceUsers, ListOrganizationServiceUsers, ListAllServiceUsers, ListProjectServiceUsers, ListServiceUserProjects, ListServiceUserCredentials not found / dropped
Service user auth Basic auth as a hidden service user; Basic auth with a hidden credential on a live service user unauthenticated, live credential still works
Writes by id UpdateGroup, DisableGroup, DeleteGroup, SetGroupMemberRole, UpdateOrganizationRole, DeleteOrganizationRole, DeleteServiceUser not found, row untouched
Name reuse CreateOrganizationDomain, CreateGroup with a hidden name new live row
Role upsert CreateOrganizationRole with a hidden name returns the old id, row stays deleted (moves with the unique constraint, as described)
SpiceDB-backed checks CheckResourcePermission through a hidden group still true, tuples untouched

One gap worth planning for before service users can be soft-deleted:

  • DeleteOrganization with a hidden service user leaves the org half deleted. The deleter lists service users through the live filter, sees none, deletes every policy on the org, then the org row delete hits the trg_serviceusers_block_org_delete trigger, which counts soft-deleted rows too. The call returns an internal error. The deleter is not transactional, so the org row stays with zero policies, and the owner is locked out. Either the trigger should ignore rows with deleted_at set, or the deleter should purge hidden service users. Not this PR, but it belongs with the service user delete work.

Pre-existing internal errors that soft delete makes easier to hit. Each returns 500 instead of not found today, even for a random uuid:

  • GetOrganizationDomain, VerifyOrganizationDomain, DeleteOrganizationDomain: the domain repository does not map no rows to domain.ErrNotExist.
  • CreatePolicy with a missing role id: the handler does not map role.ErrNotExist.
  • Bearer access token for a missing service user: the authenticator returns the raw error instead of unauthenticated.

Minor: CreateServiceUserCredential succeeds under a hidden service user (the credential cannot authenticate). ListServiceUserCredentials still lists a hidden service user's credentials. DeleteServiceUserCredential on a hidden credential hard-deletes it and reports success. ListProjectGroups and ListProjectServiceUsers drop the principal from the list but keep it in role_pairs.

No behaviour change today since no row has deleted_at set.

@whoAbhishekSah
whoAbhishekSah merged commit 4ca8187 into main Sep 23, 2026
8 checks passed
@whoAbhishekSah
whoAbhishekSah deleted the soft-delete-live-reads-tenancy branch September 23, 2026 09:49

This branch was successfully deployed

1 active deployment
Preview — 9e7797aa Deployed Sep 22, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants