Skip to content

refactor(deleter): delegate org removal cascade to membership#1696

Open
rohilsurana wants to merge 3 commits into
mainfrom
removal-cascade-consolidation
Open

refactor(deleter): delegate org removal cascade to membership#1696
rohilsurana wants to merge 3 commits into
mainfrom
removal-cascade-consolidation

Conversation

@rohilsurana

Copy link
Copy Markdown
Member

Summary

deleter.RemoveUsersFromOrg reimplemented the org-member removal cascade inline (policy walks over projects/groups/resources), duplicating logic that membership.RemoveOrganizationMember already handles canonically. This PR consolidates the removal path and deletes the now-orphaned inline implementations.

Changes

  • membership.ForceRemoveOrganizationMember (new): same cascade as RemoveOrganizationMember but without the member-and-owner guards — no ErrNotMember when the principal has no org policies, and it removes the org's last owner without complaint. Deletion cascades need this: deleting a user who solely owns an org must succeed (the org is left ownerless, same as today's behavior). Anything user-initiated keeps going through the guarded variant. Implemented by parameterizing the existing method; the cascade internals already supported unguarded owner deletes.
  • deleter.RemoveUsersFromOrg: now cleans up custom-resource policies (outside the membership cascade's scope) and delegates everything else to ForceRemoveOrganizationMember per user. Also fixes a latent bug where the policy-list error check tested the wrong variable (err instead of policyErr).
  • Deleted organization.Service.RemoveUsers and group.Service.RemoveUsers (+ its private helper): the deleter was their only caller — API handlers already use the membership service directly. Dropped the org service's now-unused AuditRecordRepository dependency and extractPrincipalInfo helper, and removed dead method declarations from the deleter/API interfaces (CascadeDeleter.RemoveUsersFromOrg had no handler callers).

Behavior notes

  • Org-member-removed audit records (auditrecord + legacy stream) are still emitted with the same shape — now by the membership cascade instead of org.RemoveUsers.
  • Group-scoped policy cleanup during user deletion previously emitted a per-group GroupMemberRemovedEvent legacy audit via group.RemoveUsers; the membership cascade removes the same policies/relations but emits the org-level removal audit instead. User deletion itself remains separately audited.
  • Net: -264 LOC.

Test plan

  • New unit tests: membership.ForceRemoveOrganizationMember (last-owner bypass, no-ErrNotMember cascade, error propagation)
  • New unit tests: deleter.RemoveUsersFromOrg (membership delegation, custom-resource cleanup incl. out-of-org and missing resources, error propagation)
  • go test -race ./core/... ./internal/api/... ./cmd/... passes
  • make lint clean
  • e2e regression (CI)

@vercel

vercel Bot commented Jun 11, 2026

Copy link
Copy Markdown

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

Project Deployment Actions Updated (UTC)
frontier Ready Ready Preview, Comment Jun 12, 2026 10:56am

@coderabbitai

coderabbitai Bot commented Jun 11, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: b051ba41-7aa5-4918-8116-a00cd7e16c6f

📥 Commits

Reviewing files that changed from the base of the PR and between c0ebbe4 and 1cfd977.

📒 Files selected for processing (2)
  • core/membership/service.go
  • core/membership/service_test.go
🚧 Files skipped from review as they are similar to previous changes (2)
  • core/membership/service_test.go
  • core/membership/service.go

📝 Walkthrough

Summary by CodeRabbit

  • Bug Fixes

    • Improved cascade deletion for organization member removal to better clean up policies and relations and surface aggregated errors.
    • Fixed edge cases so cascading deletions proceed when referenced resources are missing or belong outside the org.
  • New Features

    • Added an unguarded "force remove" path to allow member removals during deletion cascades without last-owner constraints.
  • Refactor

    • Simplified member-removal flow and removed audit-record dependency to streamline deletion orchestration.
  • Tests

    • Added coverage for force-remove and cascade deletion behaviors.

Walkthrough

This PR centralizes cascade user deletion: it removes RemoveUsers from group/org contracts, adds membership.ForceRemoveOrganizationMember (unguarded cascade), drops org audit-record dependency, rewrites the deleter to pre-clean resource-scoped policies, and updates mocks and wiring.

Changes

Member removal cascade refactoring

Layer / File(s) Summary
Interface contract updates
internal/api/v1beta1connect/interfaces.go, core/deleter/service.go
GroupService and CascadeDeleter interfaces drop RemoveUsers/RemoveUsersFromOrg. MembershipService adds ForceRemoveOrganizationMember.
Organization service audit removal
core/organization/service.go, core/organization/service_test.go
Removed AuditRecordRepository, extractPrincipalInfo, and RemoveUsers; updated NewService signature and tests to pass RoleService instead of audit repo.
Group service RemoveUsers removal
core/group/service.go
Deleted public RemoveUsers and internal removeUsers helper (policy deletions, relation deletions, group audit logging removed).
Membership service unguarded removal
core/membership/service.go, core/membership/service_test.go
Added ForceRemoveOrganizationMember (unguarded cascade entrypoint). RemoveOrganizationMember delegates to shared internal logic with guarded=true; owner/ErrNotMember checks are conditional. Includes new tests.
Deleter service removal orchestration
core/deleter/service.go, core/deleter/service_test.go
RemoveUsersFromOrg rewritten to delete only custom-resource policies for org-owned project resources, then call membership.ForceRemoveOrganizationMember per user; errors aggregated with errors.Join. Added TestRemoveUsersFromOrg.
Wiring and mock updates
cmd/serve.go, core/deleter/mocks/*, internal/api/v1beta1connect/mocks/*
Removed auditRecordRepository from organization.NewService in cmd/serve.go. Regenerated mocks: added ForceRemoveOrganizationMember mock; removed RemoveUsers mocks from organization/group/cascade mocks.

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • raystack/frontier#1590: Introduces owner-guard logic for last-owner constraints referenced by the membership removal changes.
  • raystack/frontier#1537: Earlier changes to the membership service that this PR extends with ForceRemoveOrganizationMember.
  • raystack/frontier#1471: Related constructor wiring changes to organization.NewService referenced by this PR.

Suggested reviewers

  • whoAbhishekSah
  • AmanGIT07
🚥 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.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


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 and usage tips.

@rohilsurana rohilsurana marked this pull request as ready for review June 11, 2026 08:18
@coveralls

coveralls commented Jun 11, 2026

Copy link
Copy Markdown

Coverage Report for CI Build 27411342262

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.3%) to 43.56%

Details

  • Coverage increased (+0.3%) from the base build.
  • Patch coverage: 4 uncovered changes across 2 files (45 of 49 lines covered, 91.84%).
  • 129 coverage regressions across 6 files.

Uncovered Changes

File Changed Covered %
core/deleter/service.go 17 14 82.35%
cmd/serve.go 1 0 0.0%
Total (4 files) 49 45 91.84%

Coverage Regressions

129 previously-covered lines in 6 files lost coverage.

File Lines Losing Coverage Coverage
internal/store/postgres/prospect_repository.go 58 68.18%
core/user/service.go 37 64.46%
internal/api/v1beta1connect/prospect.go 30 79.7%
core/organization/service.go 2 27.5%
core/deleter/service.go 1 74.38%
core/group/service.go 1 55.36%

Coverage Stats

Coverage Status
Relevant Lines: 37020
Covered Lines: 16126
Line Coverage: 43.56%
Coverage Strength: 12.38 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

🧹 Nitpick comments (1)
core/deleter/service_test.go (1)

261-319: ⚡ Quick win

Add a regression case for the policy-list error branch in RemoveUsersFromOrg.

Given this PR fixes the wrong-variable check around policy listing, add one subtest where policyService.List returns a non-policy.ErrNotExist error and assert it is returned/aggregated (and membership force-remove is not invoked for that user). This locks in the bug fix.

Proposed test addition
 func TestRemoveUsersFromOrg(t *testing.T) {
@@
 	t.Run("propagates membership cascade failure", func(t *testing.T) {
@@
 	})
+
+	t.Run("surfaces policy-list error for a user", func(t *testing.T) {
+		orgSvc, projSvc, resSvc, grpSvc, mbrSvc, polSvc, roleSvc, invSvc, usrSvc, patSvc, suSvc, custSvc, subSvc, invocSvc := newMocks(t)
+
+		projSvc.EXPECT().List(mock.Anything, project.Filter{OrgID: orgID}).Return([]project.Project{}, nil)
+		polSvc.EXPECT().List(mock.Anything, policy.Filter{PrincipalID: userID, PrincipalType: schema.UserPrincipal}).
+			Return(nil, errors.New("policy list boom"))
+
+		svc := deleter.NewCascadeDeleter(orgSvc, projSvc, resSvc, grpSvc, mbrSvc, polSvc, roleSvc, invSvc, usrSvc, patSvc, suSvc, custSvc, subSvc, invocSvc)
+		err := svc.RemoveUsersFromOrg(context.Background(), orgID, []string{userID})
+		assert.ErrorContains(t, err, "policy list boom")
+	})
 }

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 3d8f65d7-ee99-4517-8664-c4ba87676663

📥 Commits

Reviewing files that changed from the base of the PR and between 2834886 and 1226179.

📒 Files selected for processing (15)
  • cmd/serve.go
  • core/deleter/mocks/group_service.go
  • core/deleter/mocks/membership_service.go
  • core/deleter/mocks/organization_service.go
  • core/deleter/service.go
  • core/deleter/service_test.go
  • core/group/service.go
  • core/membership/service.go
  • core/membership/service_test.go
  • core/organization/mocks/audit_record_repository.go
  • core/organization/service.go
  • core/organization/service_test.go
  • internal/api/v1beta1connect/interfaces.go
  • internal/api/v1beta1connect/mocks/cascade_deleter.go
  • internal/api/v1beta1connect/mocks/group_service.go
💤 Files with no reviewable changes (6)
  • core/organization/mocks/audit_record_repository.go
  • core/deleter/mocks/group_service.go
  • internal/api/v1beta1connect/interfaces.go
  • internal/api/v1beta1connect/mocks/group_service.go
  • internal/api/v1beta1connect/mocks/cascade_deleter.go
  • core/group/service.go

Comment thread core/deleter/service.go
Comment thread core/membership/service.go
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