Skip to content

feat(store): policy, relation, and resource reads skip soft-deleted rows - #1936

Merged
whoAbhishekSah merged 3 commits into
mainfrom
soft-delete-live-reads-authz
Sep 22, 2026
Merged

whoAbhishekSah merged 3 commits into
mainfrom
soft-delete-live-reads-authz

Conversation

@whoAbhishekSah

Copy link
Copy Markdown
Member

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

What

  • Every read in the policy, relation, and resource repositories goes through fromLive, or adds live("p") directly where the query aliases the table.
  • The policy repository's small lookups of organization, project, and group titles for audit records use fromLive too.
  • The two updates keyed by id add the live filter.
  • The ON CONFLICT upserts are unchanged. They move together with the unique constraints in a later PR.

Behaviour change

None today. No row has deleted_at set yet.

Tested

One new test per repository: soft-delete a seeded row, then check every get, list, count, and update skips it. Store package passes with -race.

Every read in the three repositories goes through fromLive, or adds the live
filter directly where the query aliases the table. The policy repository's
small lookups of organization, project, and group titles for audit records
use fromLive too. The two updates keyed by id add the filter as well.

The ON CONFLICT upserts are unchanged. They move with the unique constraints
in a later change.
@vercel

vercel Bot commented Sep 18, 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 5:46am UTC

@coderabbitai

coderabbitai Bot commented Sep 18, 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: c2967543-81da-498e-981b-eb00f1dec753

📥 Commits

Reviewing files that changed from the base of the PR and between 6d0ae34 and 55e77d3.

📒 Files selected for processing (4)
  • internal/store/postgres/policy_repository.go
  • internal/store/postgres/policy_repository_test.go
  • internal/store/postgres/relation_repository_test.go
  • internal/store/postgres/resource_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 policies, relations, and resources are now excluded from retrieval and listing results.
    • Attempts to retrieve or update soft-deleted records now correctly report that they do not exist.
    • Counts and membership-related results no longer include soft-deleted policies.
    • Reusing an identifier from a soft-deleted resource now correctly reports a conflict.
  • Tests

    • Added coverage verifying consistent handling of soft-deleted records across repository operations.

Walkthrough

PostgreSQL policy, relation, and resource repositories now use live-row filters for reads and applicable updates. Tests verify that soft-deleted records are excluded and return not-exist or conflict errors where applicable.

Changes

Repository live-row filtering

Layer / File(s) Summary
Policy live-row filtering
internal/store/postgres/policy_repository.go, internal/store/postgres/policy_repository_test.go
Policy lists, counts, lookups, member counts, role filtering, and updates now use live-row predicates. Guarded deletion counts only live holders.
Relation live-row filtering
internal/store/postgres/relation_repository.go, internal/store/postgres/relation_repository_test.go
Relation list and lookup queries now use fromLive. Tests verify that soft-deleted relations are excluded.
Resource live-row filtering and conflict mapping
internal/store/postgres/resource_repository.go, internal/store/postgres/resource_repository_test.go
Resource list, lookups, and updates now filter for live rows. Duplicate-key errors now map to resource.ErrConflict. Tests cover soft-deleted resource behavior.

Priority: ⬇️ Low

Estimated code review effort: 2 (Simple) | ~10 minutes

Change: Feature

Suggested reviewers: amangit07

Merge Risk: ⚪ Minimal · up to 55e77

Repository operations now hide soft-deleted records while preserving expected conflict and guarded-deletion behavior. The covered tests show no actionable merge-blocking risk remains.

🚥 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

coveralls commented Sep 18, 2026 •

Copy link
Copy Markdown

Coverage Report for CI Build 35692027216

Warning

Build has drifted: This PR's base is out of sync with its target branch, so coverage data may include unrelated changes.
Quick fix: rebase this PR. Learn more →

Coverage increased (+0.4%) to 50.738%

Details

  • Coverage increased (+0.4%) from the base build.
  • Patch coverage: 2 uncovered changes across 2 files (17 of 19 lines covered, 89.47%).
  • 5 coverage regressions across 2 files.

Uncovered Changes

File Changed Covered %
internal/store/postgres/policy_repository.go 9 8 88.89%
internal/store/postgres/relation_repository.go 4 3 75.0%
Total (3 files) 19 17 89.47%

Coverage Regressions

5 previously-covered lines in 2 files lost coverage.

File Lines Losing Coverage Coverage
internal/store/postgres/org_serviceuser_repository.go 4 93.28%
core/aggregates/orgserviceuser/orgserviceuser.go 1 0.0%

Coverage Stats

Coverage Status
Relevant Lines: 41103
Covered Lines: 20855
Line Coverage: 50.74%
Coverage Strength: 16.49 hits per line

💛 - Coveralls

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1


ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Advanced

Run ID: afc335bf-b2e4-4815-9d2b-9a3083fff1eb

📥 Commits

Reviewing files that changed from the base of the PR and between f840f22 and 141a5ae.

📒 Files selected for processing (6)
  • internal/store/postgres/policy_repository.go
  • internal/store/postgres/policy_repository_test.go
  • internal/store/postgres/relation_repository.go
  • internal/store/postgres/relation_repository_test.go
  • internal/store/postgres/resource_repository.go
  • internal/store/postgres/resource_repository_test.go

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

@whoAbhishekSah

Copy link
Copy Markdown
Member Author

Tested live on a local server built from this branch (main with #1931 merged). Fixtures: a user with an org viewer policy, a project viewer policy, and a platform admin relation, plus two custom resources under a live project. Ran every affected RPC before and after setting deleted_at directly in the DB, then exercised the write paths.

Path RPCs Result
Policy reads GetPolicy, ListPolicies not found / empty
Role and count reads off the policies table ListProjectUsers and ListOrganizationUsers with roles, ListOrganizationProjects with member count, ListOrganizationsByCurrentUser, ListProjectsByCurrentUser hidden policy dropped, counts drop by one
Relation reads GetRelation, ListPlatformUsers, ListRelations not found / row dropped
Resource reads and id updates GetProjectResource, ListProjectResources, ListResources, UpdateProjectResource, DeleteProjectResource not found / row dropped
SpiceDB-backed checks CheckResourcePermission on org, project, hidden resource; superuser check for the hidden platform relation still true, tuples untouched

Write paths against a hidden row, worth noting for the phased plan:

  • CreatePolicy, AddPlatformUser, and CreateProjectResource hit ON CONFLICT on the hidden row. Each returns success with the old id, and the row keeps deleted_at, so a Get on that id says not found. The resource title was even updated in place. This moves with the unique constraints, as the description says, but until then a caller gets a success and a ghost.
  • RemovePlatformUser and DeleteRelation return success but do nothing. The relation service looks the row up through the live filter first, finds nothing, and returns nil without deleting the SpiceDB tuple. The user stayed a superuser. A not found would be safer than a silent success.
  • DeletePolicy returns not found and RemoveOrganizationMember reports not a member while SpiceDB still grants. Consistent with the reads. The cascade work owns the tuple cleanup.

No behaviour change today since no row has deleted_at set.

The create upsert targets urn only. An insert that reuses an existing id with
a different urn fails on the primary key and fell through to a raw duplicate
key error. It now maps to resource.ErrConflict, which the handler already
turns into a conflict response. With live-only reads the id of a soft-deleted
row is invisible to the pre-check, so this path is easier to reach.
@AmanGIT07

Copy link
Copy Markdown
Contributor

About RemovePlatformUser and DeleteRelation returning success but doing nothing on a hidden row, from your testing note: is that fix meant for this PR, or is it planned for later? If later, which change will pick it up?

…eads

The last-owner guard counted soft-deleted holders of the guarded role, so a
deleted owner policy would have let the last live owner be removed. The
locked CTE now counts live rows only, with a test that soft-deletes one of
the holders.

The lookups of organization, project, and group titles for the policy audit
record go back to the plain From. They only attribute the audit record, and
the trail must stay attributed after the parent is deleted.

The roles join in the policy list query only matches live roles.

The relation and resource tests list with no filter and compare counts, so
the check runs on real rows instead of an empty result.
@whoAbhishekSah

Copy link
Copy Markdown
Member Author

On RemovePlatformUser and DeleteRelation returning success on a hidden row: not this PR. The relation service already returned nil for a relation that was never there, so this is the existing behaviour showing up on hidden rows. Future work on relation writes will decide whether a miss should become not found. The org delete cascade removes memberships that may already be gone, so that decision needs to check it does not rely on the delete being repeatable.

@whoAbhishekSah
whoAbhishekSah merged commit 64aa400 into main Sep 22, 2026
8 checks passed
@whoAbhishekSah
whoAbhishekSah deleted the soft-delete-live-reads-authz branch September 22, 2026 09:34

This branch was successfully deployed

1 active deployment
Preview — 55e77d3f 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