Skip to content

improvement(governance): workspace-org invitation system consolidation #4230

Open
icecrasher321 wants to merge 14 commits intostagingfrom
feat/org-improv-big
Open

improvement(governance): workspace-org invitation system consolidation #4230
icecrasher321 wants to merge 14 commits intostagingfrom
feat/org-improv-big

Conversation

@icecrasher321
Copy link
Copy Markdown
Collaborator

Summary

Organization - Workspace consolidation for UX improvement. Adds functionality like ownership transferring, billing improvements, ux improvements, etc.

Type of Change

  • Other: UX Improvement

Testing

Tested manually

Checklist

  • Code follows project style guidelines
  • Self-reviewed my changes
  • Tests added/updated and passing
  • No new warnings introduced
  • I confirm that I have read and agree to the terms outlined in the Contributor License Agreement (CLA)

Resolve conflicts from staging's test-mock centralization and @sim/utils
extraction:

- Keep ours for invitation unification: organizations/[id]/invitations,
  members, and workspaces/invitations routes use @/lib/invitations/send +
  @/lib/invitations/core helpers (staging still referenced the dropped
  workspaceInvitation table and inline sendEmail).
- Keep ours for the deleted /api/organizations/[id]/invitations/[invitationId]
  and /api/workspaces/invitations/[invitationId] endpoints, replaced by
  the unified /api/invitations/[id]/... routes.
- Keep ours for ownership transfer infra in lib/billing/organization.ts
  (attachOwnedWorkspacesToOrganization import) and
  lib/billing/organizations/membership.ts (revokeWorkspaceCredentialMemberships,
  transferOrganizationOwnership, isSoleOwnerOfPaidOrganization,
  removeUserFromOrganization rewrite).
- Take staging for users/me/subscription/transfer imports (already had
  @sim/utils/errors) and v1/admin/organizations.
- Take staging's version of lib/workspaces/lifecycle.test.ts to adopt
  the new centralized schemaMock pattern.

Test pattern migration:
- Add @sim/utils to apps/sim/package.json dependencies so
  generateId/toError resolve via workspace link.
- Swap @/lib/core/utils/uuid imports to @sim/utils/id across
  invitations/core, invitations/send, billing/organizations/*, and
  workspaces/organization-workspaces (uuid.ts was deleted on staging).
- Extend packages/testing/src/mocks/schema.mock.ts to expose the
  post-unification schema: invitationKindEnum, invitationStatusEnum,
  updated invitation (kind/token/updatedAt), invitationWorkspaceGrant,
  workspaceModeEnum, and workspace.organizationId + workspace.workspaceMode.
- Migrate our authored tests to centralized mocks per sim-testing.mdc:
  - schemaMock for @sim/db/schema (drops per-test local schema defs)
  - authMock + authMockFns for @/lib/auth in the invitations route test
  - permissionsMock + permissionsMockFns for getWorkspaceWithOwner
  - auditMock for @/lib/audit/log
  - Drop redundant drizzle-orm and @sim/logger local mocks (globally
    mocked via vitest.setup.ts)

All 5326 tests across 308 files pass, typecheck clean, biome clean.

Made-with: Cursor
@vercel
Copy link
Copy Markdown

vercel bot commented Apr 19, 2026

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

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
docs Skipped Skipped Apr 19, 2026 2:08am

Request Review

@cursor
Copy link
Copy Markdown

cursor bot commented Apr 19, 2026

PR Summary

High Risk
High risk because it reworks invitation creation/acceptance/resend/cancellation paths and adds ownership transfer/active-organization side effects, impacting access control and billing/seat enforcement.

Overview
Unifies organization and workspace invitations around the shared invitation + invitationWorkspaceGrant model, adding new /api/invitations/[id] routes for view/update/cancel, plus accept/reject/resend endpoints with audit logging, token rotation, seat/plan gating, and workspace-policy validation.

Refactors org invitation creation to use the new invitation send/core helpers (single email per invite, rollback on send failure, partial-failure reporting), removes the legacy org and workspace invitation routes/tests, and adds a new org roster endpoint that returns members plus pending invites with per-workspace access.

Adds explicit ownership transfer APIs (user and admin) and hardens org/session/billing flows: block Better Auth org mutation endpoints, auto-activate an org after subscription upgrade, prevent billed-account changes on org workspaces, clear active org on self-removal, include pending invites in seat reduction checks, validate org slug updates in admin API, and updates docs/pricing copy to clarify workspace limits and shared workspaces.

Reviewed by Cursor Bugbot for commit 37eb540. Configure here.

Comment thread apps/sim/app/api/invitations/[id]/route.ts Outdated
Comment thread apps/sim/app/api/invitations/[id]/reject/route.ts
Comment thread apps/sim/app/api/invitations/[id]/route.ts Outdated
@icecrasher321
Copy link
Copy Markdown
Collaborator Author

bugbot run

Comment thread apps/sim/app/api/invitations/[id]/resend/route.ts
Comment thread apps/sim/app/api/organizations/[id]/members/[memberId]/route.ts
@icecrasher321
Copy link
Copy Markdown
Collaborator Author

bugbot run

Copy link
Copy Markdown

@cursor cursor bot left a comment

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 2 potential issues.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 37eb540. Configure here.

error: 'Some invitation emails failed to send.',
message: `${sentInvitations.length} invitation(s) sent, ${failedInvitations.length} failed`,
data: responseData,
})
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Partial batch failure returns HTTP 200 with success false

Medium Severity

When some invitation emails fail but others succeed, the response returns HTTP 200 (the default NextResponse.json status) with success: false. Clients that check only the HTTP status code would treat this as a fully successful request, silently missing that some invitations were never delivered. The all-fail case correctly returns 502, but this partial-failure path lacks an explicit non-2xx status code like 207.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 37eb540. Configure here.

grantUpdates: grantsToApply,
},
request,
})
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

PATCH audit always uses org type for workspace invitations

Low Severity

The PATCH handler unconditionally records AuditAction.ORG_INVITATION_UPDATED with AuditResourceType.ORGANIZATION regardless of inv.kind. For workspace-scoped invitations, this produces misleading audit entries. The sibling DELETE handler in the same file correctly branches on inv.kind to choose between workspace and organization audit actions.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 37eb540. Configure here.

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.

1 participant