Skip to content

refactor: centralize org member additions via membership package#1548

Merged
whoAbhishekSah merged 19 commits intomainfrom
feat/remove-add-org-users-rpc
Apr 21, 2026
Merged

refactor: centralize org member additions via membership package#1548
whoAbhishekSah merged 19 commits intomainfrom
feat/remove-add-org-users-rpc

Conversation

@whoAbhishekSah
Copy link
Copy Markdown
Member

@whoAbhishekSah whoAbhishekSah commented Apr 17, 2026

Summary

Deletes the AddOrganizationUsers RPC (no SDK usage, replaced by AddOrganizationMembers) and migrates all callers of org.AddMember / org.AddUsers to use membership.AddOrganizationMember. Both functions are then deleted from the organization service.

Key changes

Bug fixes

  • Invitation accept no longer stacks roles. Old flow created 2 policies (hardcoded viewer + invite role). New flow creates 1 policy with the invite's role directly. If no role is specified, falls back to viewer.
  • Disabled-org creation no longer leaves ownerless rows. When PlatformDisableOrgsOnCreate is true, the org is now created as Enabled, membership + platform setup completes, then it's set to Disabled. Previously, the disabled org was persisted first and AddOrganizationMember rejected it.
  • Service users can no longer create organizations. org.Create now returns ErrUserPrincipalOnly for non-user principals.

Optimizations

  • Fewer SpiceDB calls on domain auto-join. Dropped a redundant ListByUser (SpiceDB LookupResources) — AddOrganizationMember already checks membership internally and returns ErrAlreadyMember.
  • policyService removed from invitation service entirely. All policy+relation operations go through the membership package.

Cleanup

  • Redundant orgService.Get removed from JoinOrganization and AcceptOrganizationInvitation handlers. Org validation now happens inside the downstream services (domain.Join, invitation.Acceptmembership.AddOrganizationMember). Error cases (ErrDisabled, ErrNotExist) are still surfaced to the client via errors.Is matching.
  • Deleted: AddOrganizationUsers handler + auth interceptor entry, org.AddUsers(), org.AddMember(), mapPrincipalTypeToAuditType(), AddMember from domain/invitation OrgService interfaces, raw policyService.Create loop in invitation accept.

Behavioral changes

Call site Before After
org.Create AddMember("owner") — no validation membership.AddOrganizationMember(owner) — validates user, role, org state
org.AdminCreate Same Same
invitation.Accept AddMember(viewer) + policyService.Create(inviteRole) = 2 policies membership.AddOrganizationMember(inviteRole) = 1 policy
domain.Join ListByUser + AddMember(viewer) membership.AddOrganizationMember(viewer), ErrAlreadyMember treated as success
JoinOrganization handler orgService.Get + domainService.Join domainService.Join only (org validated internally)
AcceptOrganizationInvitation handler orgService.Get + invitationService.Accept invitationService.Accept only (org validated internally)
Disabled org creation Persisted as disabled → AddOrganizationMember rejects → ownerless row Created enabled → setup completes → then disabled
Service user creates org Succeeded (broken — no valid membership) Rejected with ErrUserPrincipalOnly

Test updates

  • Migrated all 14 e2e AddOrganizationUsers calls to AddOrganizationMembers
  • Added requireAddOrgMembersSuccess helper to assert per-member results (not just RPC error)
  • Updated unit test mocks for JoinOrganization and AcceptOrganizationInvitation (removed stale orgService.Get expectations)
  • Updated disabled org + service user org creation tests to expect failure

Related

🤖 Generated with Claude Code

@vercel
Copy link
Copy Markdown

vercel bot commented Apr 17, 2026

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

Project Deployment Actions Updated (UTC)
frontier Ready Ready Preview, Comment Apr 21, 2026 6:08am

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 17, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Introduces a MembershipService abstraction and moves organization membership creation to it; removes legacy AddMember/AddUsers flows and related mocks/handlers; updates service constructors/wiring, mocks, handlers, error mappings, and tests to use AddOrganizationMember and membership-centered validation.

Changes

Cohort / File(s) Summary
Service interfaces & implementations
core/domain/service.go, core/organization/service.go, core/invitation/service.go
Add MembershipService interface and dependency; remove OrgService.AddMember/AddUsers; replace internal member creation with AddOrganizationMember(...); remove previous policy/relation-based AddMember logic; add SetMembershipService(...).
Service wiring
cmd/serve.go
Wire membership service into organization/domain/invitation services (SetMembershipService(...) and pass membershipService to constructors).
Mocks added
core/invitation/mocks/membership_service.go, core/organization/mocks/membership_service.go
Add autogenerated mocks for new MembershipService (expecter, call helpers, NewMembershipService).
Mocks removed/updated
core/invitation/mocks/organization_service.go, internal/api/v1beta1connect/mocks/organization_service.go
Remove mock methods/helpers for deleted AddMember/AddUsers.
API handlers & interfaces
internal/api/v1beta1connect/interfaces.go, internal/api/v1beta1connect/organization.go, internal/api/v1beta1connect/invitations.go, internal/api/v1beta1connect/domain.go
Remove AddUsers from exported OrganizationService; delete AddOrganizationUsers handler; remove upfront org Get in some handlers and rely on membership service validation; extend error mappings to include membership/org/user-related errors.
Authorization interceptors
pkg/server/connect_interceptors/authorization.go
Remove authorization validation entry for AddOrganizationUsers endpoint.
Tests updated/removed
core/invitation/service_test.go, core/organization/service_test.go, internal/api/v1beta1connect/*.go tests, test/e2e/regression/*, internal/api/v1beta1connect/invitations_test.go, internal/api/v1beta1connect/domain_test.go
Update tests to inject/expect MembershipService mock; remove tests for deleted APIs; change e2e membership calls to AddOrganizationMembers with role IDs; adjust assertions and setup accordingly.
Errors & validations
core/organization/errors.go
Add ErrUserPrincipalOnly and reformat error variables.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested reviewers

  • rohilsurana
  • rsbh
  • AmanGIT07

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.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
core/organization/service.go (1)

89-112: ⚠️ Potential issue | 🟠 Major

Nil-pointer risk: membershipService is not enforced before use.

NewService doesn't accept membershipService, and the field defaults to nil. Any caller that constructs the service via NewService and then calls Create (line 217) or AdminCreate (line 463) before SetMembershipService(...) is invoked will hit a nil-pointer dereference inside AddOrganizationMember. cmd/serve.go wires it correctly today, but tests, CLI tools, or future call sites can easily miss the setter — and the failure mode is a panic at request time rather than an init-time error.

Consider one of the following hardenings:

Option A — Defensive check with clear error (minimal change)
 func (s Service) Create(ctx context.Context, org Organization) (Organization, error) {
+  if s.membershipService == nil {
+    return Organization{}, fmt.Errorf("organization service: membership service not configured")
+  }
   principal, err := s.authnService.GetPrincipal(ctx)

(and the analogous guard at the top of AdminCreate).

Option B — Return an error from the setter and validate wiring at startup
-func (s *Service) SetMembershipService(ms MembershipService) {
-  s.membershipService = ms
+func (s *Service) SetMembershipService(ms MembershipService) error {
+  if ms == nil {
+    return errors.New("membership service is nil")
+  }
+  s.membershipService = ms
+  return nil
 }

This at least surfaces misuse at the wiring boundary in cmd/serve.go.

Option A is the safer runtime guard; Option B catches misconfiguration earlier. Either is preferable to a silent panic.

🧹 Nitpick comments (2)
core/invitation/service_test.go (1)

73-75: Consider adding a test for the new Accept membership path.

This PR rewires Accept(...) to call membershipSvc.AddOrganizationMember(...) with schema.RoleOrganizationViewer, but no test asserts that expectation. Adding a TestService_Accept case that mocks membershipSvc.EXPECT().AddOrganizationMember(ctx, orgID, userID, schema.UserPrincipal, schema.RoleOrganizationViewer).Return(nil) would lock in the behavior and guard against regressions (e.g., role or principal type drift).

core/domain/service.go (1)

32-34: Interface declaration — consider consolidating.

The same MembershipService interface shape (AddOrganizationMember(ctx, orgID, principalID, principalType, roleID) error) is now declared independently in core/domain/service.go, core/organization/service.go, and core/invitation/service.go. That's idiomatic Go (consumer-defined interfaces), but if the contract evolves (e.g., a bulk variant or additional parameters), you'll need to update each site. Fine to keep as-is; worth noting for future consistency.


ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 73cd3d8b-e244-4537-9b5e-2828bb72d5a8

📥 Commits

Reviewing files that changed from the base of the PR and between a8a19f3 and 3d1f062.

📒 Files selected for processing (14)
  • cmd/serve.go
  • core/domain/service.go
  • core/invitation/mocks/membership_service.go
  • core/invitation/mocks/organization_service.go
  • core/invitation/service.go
  • core/invitation/service_test.go
  • core/organization/mocks/membership_service.go
  • core/organization/service.go
  • core/organization/service_test.go
  • internal/api/v1beta1connect/interfaces.go
  • internal/api/v1beta1connect/mocks/organization_service.go
  • internal/api/v1beta1connect/organization.go
  • internal/api/v1beta1connect/organization_test.go
  • pkg/server/connect_interceptors/authorization.go
💤 Files with no reviewable changes (7)
  • pkg/server/connect_interceptors/authorization.go
  • internal/api/v1beta1connect/interfaces.go
  • core/organization/service_test.go
  • internal/api/v1beta1connect/organization_test.go
  • internal/api/v1beta1connect/organization.go
  • internal/api/v1beta1connect/mocks/organization_service.go
  • core/invitation/mocks/organization_service.go

@coveralls
Copy link
Copy Markdown

coveralls commented Apr 17, 2026

Coverage Report for CI Build 24706930225

Coverage decreased (-0.1%) to 42.065%

Details

  • Coverage decreased (-0.1%) from the base build.
  • Patch coverage: 68 uncovered changes across 7 files (9 of 77 lines covered, 11.69%).
  • 14 coverage regressions across 3 files.

Uncovered Changes

File Changed Covered %
core/organization/service.go 21 0 0.0%
core/domain/service.go 11 0 0.0%
core/invitation/service.go 12 2 16.67%
internal/api/v1beta1connect/invitations.go 10 0 0.0%
internal/api/v1beta1connect/domain.go 15 7 46.67%
cmd/serve.go 6 0 0.0%
internal/api/v1beta1connect/organization.go 2 0 0.0%

Coverage Regressions

14 previously-covered lines in 3 files lost coverage.

File Lines Losing Coverage Coverage
core/organization/service.go 9 25.17%
core/invitation/service.go 4 22.73%
core/domain/service.go 1 0.0%

Coverage Stats

Coverage Status
Relevant Lines: 36767
Covered Lines: 15466
Line Coverage: 42.06%
Coverage Strength: 11.92 hits per line

💛 - Coveralls

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

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 (3)
core/organization/service.go (1)

108-112: Consider a nil guard on membershipService to fail fast.

membershipService is injected via setter after construction, so any caller that forgets to wire it (a real risk as new bootstrap paths are added, or in tests that construct organization.Service directly) will hit a nil-pointer panic inside Create/AdminCreate only when the code path is exercised. A defensive check — either at call sites (lines 206, 453) or a validation in SetMembershipService/at the start of Create/AdminCreate — would turn a latent panic into a clear error. Not blocking; just a robustness nit given the intentional setter-injection design.

core/invitation/service_test.go (1)

36-91: Add tests for the new Accept membership flow.

The Coveralls report flags 2 of 4 changed lines in core/invitation/service.go as uncovered, and the only test here exercises the "already member" short-circuit in Create. Adding cases for Accept that verify: (a) membershipSvc.AddOrganizationMember is called with the role from invite.RoleIDs[0] when conf.WithRoles=true, (b) it falls back to schema.RoleOrganizationViewer when WithRoles=false or RoleIDs is empty, and (c) membership.ErrAlreadyMember is swallowed and the acceptance continues to group add / audit / delete, would lock in the new behavior and recover coverage.

core/invitation/service.go (1)

305-314: getConfig is re-evaluated at accept time — double-check this is intended.

orgRoleID here depends on the current value of conf.WithRoles at accept time, but invite.RoleIDs was only persisted when WithRoles was true at create time (see line 137-140 in Create). The asymmetry has two quirks:

  • If WithRoles was true at create and false at accept, invite.RoleIDs is populated but will be ignored — user silently gets viewer.
  • If WithRoles was false at create and true at accept, RoleIDs is empty anyway, so viewer is applied (fine).

The first case is probably fine as platform-level policy, but if WithRoles was the intent at creation, honoring the stored RoleIDs unconditionally (or at least logging when they are dropped at accept) would be more predictable.


ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 17415b4e-af3b-4abb-b7bd-acf7770c20f2

📥 Commits

Reviewing files that changed from the base of the PR and between 3d1f062 and 97a9695.

📒 Files selected for processing (5)
  • cmd/serve.go
  • core/domain/service.go
  • core/invitation/service.go
  • core/invitation/service_test.go
  • core/organization/service.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • core/domain/service.go

Comment thread core/invitation/service.go
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
test/e2e/regression/api_test.go (1)

330-353: ⚠️ Potential issue | 🟠 Major

Keep the disabled-org preference regression specific.

These tests now accept any create error, so they would pass if org creation returns Internal after partially persisting an ownerless disabled org. Assert the expected Connect code and the post-condition: either creation succeeds with a disabled org, or failure leaves no created org behind.

Example post-condition if failure is intended
 		_, err = s.testBench.Client.CreateOrganization(ctxOrgUserAuth, connect.NewRequest(&frontierv1beta1.CreateOrganizationRequest{
 			Body: &frontierv1beta1.OrganizationRequestBody{
 				Title: "org acme 5",
 				Name:  "org-acme-5",
 			},
 		}))
-		s.Assert().Error(err)
+		s.Require().Error(err)
+		s.Assert().NotEqual(connect.CodeInternal, connect.CodeOf(err))
+
+		_, getErr := s.testBench.Client.GetOrganization(ctxOrgAdminAuth, connect.NewRequest(&frontierv1beta1.GetOrganizationRequest{
+			Id: "org-acme-5",
+		}))
+		s.Require().Error(getErr)
+		s.Assert().Equal(connect.CodeNotFound, connect.CodeOf(getErr))

Adjust the expected code/state if the intended behavior is to create a disabled organization successfully.

Also applies to: 2473-2493


ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 1674e3c0-c366-4c87-8761-72a668578b12

📥 Commits

Reviewing files that changed from the base of the PR and between f4cad42 and f0fd69a.

📒 Files selected for processing (8)
  • core/organization/errors.go
  • core/organization/service.go
  • internal/api/v1beta1connect/domain.go
  • internal/api/v1beta1connect/invitations.go
  • internal/api/v1beta1connect/organization.go
  • test/e2e/regression/api_test.go
  • test/e2e/regression/onboarding_test.go
  • test/e2e/regression/serviceusers_test.go
✅ Files skipped from review due to trivial changes (1)
  • core/organization/errors.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • test/e2e/regression/serviceusers_test.go

Comment thread core/organization/service.go
Comment thread test/e2e/regression/api_test.go Outdated
Comment thread test/e2e/regression/onboarding_test.go Outdated
@whoAbhishekSah
Copy link
Copy Markdown
Member Author

whoAbhishekSah commented Apr 20, 2026

Manual RPC Test Report

Ran thorough local RPC tests against this branch. All behavioral claims verified.

# Test Result Key finding
T1 AddOrganizationUsers (FrontierService) Effectively dead — auth interceptor denies by default (403) for all principals since map entry was deleted. Proto still declares it (proton repo not updated), but no one can invoke it.
B.1 AddOrganizationMembers happy path, 2 members Each gets exactly one role (viewer/manager) — no stacked roles.
B.2a Already-member "principal is already a member of this resource"
B.2b Non-existent role "role doesn't exist"
B.2c Non-existent user "user doesn't exist"
B.2d Non-existent org "org doesn't exist"
B.2e Partial-success batch (mix of valid + invalid) Per-member results returned correctly
B.2f Non-admin caller 403 permission_denied (authz enforced)
B.2g Service-user ID passed as user_id "user doesn't exist" (handler hardcodes UserPrincipal)
B.2h Project-scoped role on org "role is not valid for organization scope"
B.2i Disabled user "user doesn't exist" (pre-existing repo filter behavior)
B.2j Disabled org "org is disabled"
B.2k–m Empty / malformed UUID Proto-level invalid_argument via buf.validate
B.3 Newly-added viewer can GET org but not UPDATE Relation correctly materialised in SpiceDB
C.1 org.Create by regular user Creator gets exactly owner (single role); update/delete work → owner relation in SpiceDB
C.2 org.Create by service user Rejected: "only user principals can create organizations"
F.1 AdminCreateOrganization for existing user Bob becomes sole owner
F.2 AdminCreateOrganization auto-creates new user New user auto-created + becomes sole owner
D.3 Invitation accept with non-viewer role (headline fix) Eve invited as manager → has ONLY manager. Previously created viewer + manager stacked policies.
D.4 Invite with no role Falls back to viewer
D.5 Invite accept while already member ErrAlreadyMember treated as success (200 empty); role unchanged
D.6 Invite with multiple role_ids Only first role applied (by design — multi-role is future work)
D.7 invite_with_roles=false Invite's roles wiped at creation; accept falls back to viewer
E.1 Domain auto-join User on matching domain joins as viewer (single role)
E.2 Domain rejoin (already member) Idempotent — ErrAlreadyMember swallowed, 200 empty response
E.3 Higher-role member (manager) rejoins via domain Role NOT downgraded to viewer, NOT stacked — stays manager
E.4 JoinOrganization on non-matching domain "user and org's whitelisted domains doesn't match"
E.5 JoinOrganization on disabled org "org is disabled. Please contact your administrator to enable it"
E.6 JoinOrganization on non-existent org 404 not found
G.1 disable_orgs_on_create=true + org.Create (CodeRabbit fix) Org persists as disabled; owner policy is still written (verified in DB). Before the fix, the old AddMember ran against an already-disabled org and would have left an ownerless row.
G.2 disable_orgs_on_create=true + AdminCreateOrganization Same: org disabled, owner policy present.
G.3 Sanity: AddOrganizationMembers still rejects a disabled org "org is disabled" — the validation that tripped the old bootstrap still holds; the fix works by ordering Enabled → membership → Disabled.

Minor observations (not blockers)

  • AddOrganizationUsers removal relies on auth-interceptor default-deny rather than returning unimplemented. Proto still declares the RPC (proton repo not touched), so clients see permission_denied (403), not unimplemented.
  • Disabled users surface as "user doesn't exist" rather than user.ErrDisabled — pre-existing repository-level filter, not introduced by this PR.
  • Invitation multi-role only applies the first role_ids[0]; remaining entries are ignored (intentional per code comment).

LGTM from a behavioral standpoint. The viewer + invitedRole double-policy issue is genuinely fixed end-to-end, and the CodeRabbit-flagged ownerless-disabled-org case is resolved.

whoAbhishekSah added a commit to raystack/proton that referenced this pull request Apr 20, 2026
This RPC has been superseded by AddOrganizationMembers on the AdminService,
which supports explicit role assignment per member. The server-side handler
was already removed in raystack/frontier#1548.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
whoAbhishekSah and others added 19 commits April 21, 2026 11:37
…mbers

AddOrganizationUsers had no SDK usage and was replaced by
AddOrganizationMembers (AdminService) which takes explicit roles
and returns per-member results.

Removed:
- AddOrganizationUsers handler
- AddUsers from OrganizationService interface
- org.AddUsers() service function
- Auth interceptor entry
- Handler tests

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…ationMember

org.Create and AdminCreate now call membership.AddOrganizationMember
instead of org.AddMember to add the creator as owner. Membership
dependency injected via SetMembershipService() setter to break
circular init order.

Behavioral change: new code validates user exists+enabled and role
scope. Old code trusted the authn principal blindly. Non-user
principals (serviceuser/PAT) are now rejected — org creation is
user-only in practice.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
invitation.Accept now calls membershipSvc.AddOrganizationMember
instead of orgSvc.AddMember to add user on invitation acceptance.
Uses RoleOrganizationViewer (same as old MemberRole).

No behavioral change in practice — caller already checks
isUserOrgMember before calling, so ErrAlreadyMember won't trigger.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Domain auto-join now calls membershipSvc.AddOrganizationMember
instead of orgSvc.AddMember. Uses RoleOrganizationViewer.
Removed AddMember from domain's OrgService interface.

No behavioral change in practice — caller already checks
ListByUser for existing membership before calling.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…ationMemberRole

Add comments at each call site explaining that Add is used because
the user is not yet a member (org just created, or verified not a
member by the caller). Set would fail with ErrNotMember.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
AddOrganizationMember already checks if the user is a member and
returns ErrAlreadyMember. No need for a separate ListByUser call
before Add — just handle the error. Saves a SpiceDB LookupResources
round-trip on the happy path.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…om invitation

Accept invitation now uses the first role from invite.RoleIDs (falls
back to viewer if empty) via membership.AddOrganizationMember. This
replaces the old two-step flow: hardcoded viewer AddMember + raw
policyService.Create loop for invite roles.

Also removes policyService dependency from invitation service entirely
— one fewer cross-service dependency.

If multiple role support is needed in the future, the comment at the
role selection marks where to change.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
org.Create now rejects non-user principals upfront. Service users
should not create organizations — they are bound to an existing org
at creation time.

Updated e2e test to expect failure instead of success when a service
user attempts to create an org.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…embers

All 14 e2e test calls (12 in api_test.go, 2 in onboarding_test.go)
migrated from the deleted AddOrganizationUsers RPC to the new
AddOrganizationMembers AdminService RPC with explicit viewer role.

Role UUID looked up once in SetupSuite via ListRoles.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…n handlers

AcceptOrganizationInvitation and JoinOrganization handlers called
orgService.Get before calling services that internally validate
the org via membership.AddOrganizationMember. Removed the redundant
call to save a DB round-trip.

Added missing error cases for errors that can now bubble up from
membership: user.ErrDisabled, organization.ErrDisabled,
organization.ErrNotExist, membership.ErrInvalidOrgRole,
membership.ErrAlreadyMember.

Also fixed JoinOrganization to use errors.Is instead of == for
domain.ErrDomainsMisMatch comparison.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…eptOrganizationInvitation tests

Handlers no longer call orgService.Get directly — org validation is done
internally by the downstream services (domain.Join, invitation.Accept).
Updated tests to route org-related errors through the downstream service
mocks instead.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
… setup

When PlatformDisableOrgsOnCreate is true, the org was persisted as disabled
before AddOrganizationMember ran, which rejects disabled orgs — leaving an
ownerless row. Now creates as Enabled, completes membership + platform
setup, then sets Disabled if the preference requires it.

Also adds requireAddOrgMembersSuccess helper to e2e tests to assert
per-member results from AddOrganizationMembers (not just the RPC error).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…led state

With the create-enabled-then-disable fix, org creation succeeds when
PlatformDisableOrgsOnCreate is true — the returned org is in disabled
state. Updated both test cases to assert NoError + disabled state instead
of expecting failure.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@whoAbhishekSah whoAbhishekSah force-pushed the feat/remove-add-org-users-rpc branch from 33efa91 to 708f25f Compare April 21, 2026 06:07
@whoAbhishekSah whoAbhishekSah merged commit ec2239c into main Apr 21, 2026
8 checks passed
@whoAbhishekSah whoAbhishekSah deleted the feat/remove-add-org-users-rpc branch April 21, 2026 06:37
whoAbhishekSah added a commit to raystack/proton that referenced this pull request Apr 21, 2026
This RPC has been superseded by AddOrganizationMembers on the AdminService,
which supports explicit role assignment per member. The server-side handler
was already removed in raystack/frontier#1548.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
whoAbhishekSah added a commit to raystack/proton that referenced this pull request Apr 21, 2026
This RPC has been superseded by AddOrganizationMembers on the AdminService,
which supports explicit role assignment per member. The server-side handler
was already removed in raystack/frontier#1548.

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
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