docs/plans/saas/06.saas-vs-selfhosted.md
This document catalogs every code path where SaaS and self-hosted modes diverge, based on profile.SaaS checks.
backend/api/v1/user_service.go:192CodeUnimplemented). Users are added via workspace IAM policy.bb.users.create permission.backend/api/v1/user_service.go:338CodePermissionDenied because principals are global (shared across workspaces).bb.users.update permission can update other users.backend/api/v1/user_service.go:490CodeUnimplemented).backend/api/v1/user_service.go:577CodeUnimplemented).backend/api/v1/user_service.go:630CodeUnimplemented).bb.users.updateEmail permission.backend/api/v1/user_service.go:555allUsers binding is never counted as admin coverage.allUsers binding means every principal is an admin; counts all active principals via CountAllActivePrincipals.backend/api/v1/auth_service.go:199-230Finding workspaces (line 199):
FindWorkspacesByMemberEmail(ctx, email, false) — only explicit membership.FindWorkspacesByMemberEmail(ctx, email, true) — includes allUsers workspaces.Fallback when not a member (line 208-217):
workspaceID stays empty → new workspace created.store.GetWorkspaceID() to auto-join the single existing workspace.DisallowSignup check (line 220-229):
disallow_signup (gated by FEATURE_DISALLOW_SELF_SERVICE_SIGNUP license).backend/api/v1/auth_service.go:947FindWorkspaceIDByMemberEmail(ctx, email, false) — explicit membership only.FindWorkspaceIDByMemberEmail(ctx, email, true) — includes allUsers.backend/api/auth/auth.go:251allUsers is never a valid principal for workspace membership.allUsers is valid — any principal is considered a member.backend/api/v1/workspace_service.go:70allUsers with error: "allUsers is not allowed in workspace IAM policy in SaaS mode, add members explicitly".allUsers allowed in bindings.backend/component/iam/manager.go:51allUsers is skipped (skipAllUsers=true). At project level, allUsers means "all workspace members" (still allowed).allUsers grants permission at both workspace and project levels.backend/api/v1/setting_service.go:210FEATURE_DISALLOW_SELF_SERVICE_SIGNUP).backend/api/v1/setting_service.go:220--external-url).backend/component/config/profile.go:63saasFeatureControlMap (currently empty). Reserved for future SaaS-specific feature gating.backend/api/v1/subscription_service.go:42StoreLicense().backend/enterprise/license.go:414return nil). Replica scaling managed by operator.backend/api/v1/instance_service.go:281iam_extension set.backend/server/server.go:157sampleInstanceManager is not initialized.backend/api/v1/actuator_service.go:135backend/api/oauth2/oauth2.go:67/api/oauth2/register, /api/oauth2/authorize, etc.) are registered for backwards compatibility.backend/api/oauth2/oauth2.go:82store.GetWorkspaceID() if workspace ID not in URL.backend/api/v1/actuator_service.go:60store.GetWorkspaceID() to get the single workspace.backend/api/v1/actuator_service.go:160Saas: true/false in the response so the frontend can adjust its behavior.backend/api/v1/idp_service.go:59store.GetWorkspaceID() for unauthenticated login page (the endpoint is allow_without_credential).| Area | Self-Hosted | SaaS |
|---|---|---|
| CreateUser | Allowed (with permission) | Blocked — use workspace IAM |
| UpdateUser (other) | Allowed (with permission) | Self-updates only |
| DeleteUser / UndeleteUser / UpdateEmail | Allowed (with permission) | Blocked |
| Signup: workspace resolution | Includes allUsers, auto-joins existing | Explicit membership only, creates new workspace |
| Signup: disallow_signup | Enforced (with license) | Skipped |
| Workspace IAM: allUsers | Allowed | Blocked |
| IAM permission: allUsers | Grants permission at workspace + project level | Skipped at workspace level, allowed at project level |
| disallow_signup setting | Configurable | Cannot modify |
| external_url setting | Configurable | Cannot modify |
| License updates | Allowed | Blocked |
| Replica limit | Enforced by license | Skipped (operator-managed) |
| IAM credentials | Default credentials allowed | Explicit credentials required |
| Sample instances | Available | Disabled |
| OAuth2 legacy routes | Registered | Not registered |
| Workspace ID resolution | Falls back to store | Required in URL/auth context |