v0.6.53: permissions groups migration, docs updates

This commit is contained in:
Waleed
2026-04-21 21:19:20 -07:00
committed by GitHub
7 changed files with 107 additions and 133 deletions

View File

@@ -165,7 +165,7 @@ When a user opens Mothership, their permission group is read before any block or
<FAQ items={[
{
question: "Who can create and manage permission groups?",
answer: "Any workspace admin on an Enterprise-entitled workspace can create, edit, and delete permission groups for that workspace. On Sim Cloud, the workspace's billed account must be on the Enterprise plan; on self-hosted deployments you can enable it via ACCESS_CONTROL_ENABLED."
answer: "Any workspace admin on an Enterprise-entitled workspace can create, edit, and delete permission groups for that workspace. The workspace's billed account must be on the Enterprise plan."
},
{
question: "What happens to a workflow that was built before a block was restricted?",

View File

@@ -3,7 +3,6 @@ title: Audit Logs
description: Track every action taken across your organization's workspaces
---
import { Callout } from 'fumadocs-ui/components/callout'
import { FAQ } from '@/components/ui/faq'
import { Image } from '@/components/ui/image'
@@ -78,9 +77,7 @@ Authorization: Bearer <api-key>
Paginate by passing the `nextCursor` value as the `cursor` parameter in the next request. When `nextCursor` is absent, you have reached the last page.
<Callout type="info">
The API accepts both personal and workspace-scoped API keys. Rate limits apply — the response includes `X-RateLimit-*` headers with your current limit and remaining quota.
</Callout>
The API accepts both personal and workspace-scoped API keys. Rate limits apply — the response includes `X-RateLimit-*` headers with your current limit and remaining quota.
---

View File

@@ -3,7 +3,6 @@ title: Data Retention
description: Control how long execution logs, deleted resources, and copilot data are kept before permanent deletion
---
import { Callout } from 'fumadocs-ui/components/callout'
import { FAQ } from '@/components/ui/faq'
import { Image } from '@/components/ui/image'
@@ -55,9 +54,7 @@ Controls how long **Mothership data** is kept, including:
- Run checkpoints and async tool calls
- Inbox tasks (Sim Mailer)
<Callout type="info">
Each setting is independent. You can configure a short log retention period alongside a long soft deletion cleanup period, or set any combination that fits your compliance requirements.
</Callout>
Each setting is independent. You can configure a short log retention period alongside a long soft deletion cleanup period, or any combination that fits your compliance requirements.
---
@@ -67,23 +64,9 @@ Retention is configured at the **workspace level**, not organization-wide. Each
---
## Plan defaults
## Defaults
Non-enterprise workspaces use the following automatic defaults. These cannot be changed.
| Setting | Free | Pro | Team |
|---------|------|-----|------|
| Log retention | 30 days | Not configured | Not configured |
| Soft deletion cleanup | 30 days | 90 days | 90 days |
| Task cleanup | Not configured | Not configured | Not configured |
"Not configured" means that category of data is not automatically deleted on that plan.
Enterprise workspaces have no defaults — retention only runs for a setting once you configure it. Until configured, that category of data is not automatically deleted.
<Callout type="info">
On Enterprise, setting a period to **Forever** explicitly keeps data indefinitely. Leaving a setting unconfigured has the same effect, but setting it to Forever makes the intent explicit and allows you to change it later without needing to save from scratch.
</Callout>
By default, all three settings are unconfigured — no data is automatically deleted in any category until you configure it. Setting a period to **Forever** has the same effect as leaving it unconfigured, but makes the intent explicit and allows you to change it later without saving from scratch.
---

View File

@@ -3,7 +3,6 @@ title: Enterprise
description: Enterprise features for business organizations
---
import { Callout } from 'fumadocs-ui/components/callout'
import { FAQ } from '@/components/ui/faq'
Sim Enterprise provides advanced features for organizations with enhanced security, compliance, and management requirements.
@@ -26,9 +25,9 @@ Define permission groups on a workspace to control what features and integration
2. Create a permission group with your desired restrictions
3. Add workspace members to the permission group
<Callout type="info">
Any workspace admin on an Enterprise-entitled workspace can manage permission groups. Users not assigned to any group have full access. Permission restrictions are enforced at both UI and execution time, and apply to workflows based on the workflow's workspace.
</Callout>
Any workspace admin on an Enterprise-entitled workspace can manage permission groups. Users not assigned to any group have full access. Restrictions are enforced at both UI and execution time, based on the workflow's workspace.
See the [Access Control guide](/docs/enterprise/access-control) for full details.
---
@@ -40,69 +39,46 @@ See the [SSO setup guide](/docs/enterprise/sso) for step-by-step instructions an
---
## Self-Hosted Configuration
## Whitelabeling
For self-hosted deployments, enterprise features can be enabled via environment variables without requiring billing.
Replace Sim's default branding — logos, product name, and favicons — with your own. See the [whitelabeling guide](/docs/enterprise/whitelabeling).
### Environment Variables
---
## Audit Logs
Track configuration and security-relevant actions across your organization for compliance and monitoring. See the [audit logs guide](/docs/enterprise/audit-logs).
---
## Data Retention
Configure how long execution logs, soft-deleted resources, and Mothership data are kept before permanent deletion. See the [data retention guide](/docs/enterprise/data-retention).
---
<FAQ items={[
{ question: "Who can manage Enterprise features?", answer: "Workspace admins on an Enterprise-entitled workspace. Access Control, SSO, whitelabeling, audit logs, and data retention are all configured per workspace under Settings → Enterprise." },
{ question: "Which SSO providers are supported?", answer: "Sim supports SAML 2.0 and OIDC, which works with virtually any enterprise identity provider including Okta, Azure AD (Entra ID), Google Workspace, ADFS, and OneLogin." },
{ question: "How do access control permission groups work?", answer: "Permission groups are created per workspace and let you restrict which AI providers, workflow blocks, and platform features are available to specific members of that workspace. Each user can belong to at most one group per workspace. Users not assigned to any group have full access. Restrictions are enforced at both the UI level and at execution time based on the workflow's workspace." },
]} />
---
## Self-hosted setup
Self-hosted deployments enable enterprise features via environment variables instead of billing.
| Variable | Description |
|----------|-------------|
| `ORGANIZATIONS_ENABLED`, `NEXT_PUBLIC_ORGANIZATIONS_ENABLED` | Enable team/organization management |
| `ACCESS_CONTROL_ENABLED`, `NEXT_PUBLIC_ACCESS_CONTROL_ENABLED` | Permission groups for access restrictions |
| `SSO_ENABLED`, `NEXT_PUBLIC_SSO_ENABLED` | Single Sign-On with SAML/OIDC |
| `CREDENTIAL_SETS_ENABLED`, `NEXT_PUBLIC_CREDENTIAL_SETS_ENABLED` | Polling Groups for email triggers |
| `INBOX_ENABLED`, `NEXT_PUBLIC_INBOX_ENABLED` | Sim Mailer inbox for outbound email |
| `WHITELABELING_ENABLED`, `NEXT_PUBLIC_WHITELABELING_ENABLED` | Custom branding and white-labeling |
| `AUDIT_LOGS_ENABLED`, `NEXT_PUBLIC_AUDIT_LOGS_ENABLED` | Audit logging for compliance and monitoring |
| `DISABLE_INVITATIONS`, `NEXT_PUBLIC_DISABLE_INVITATIONS` | Globally disable workspace/organization invitations |
| `ORGANIZATIONS_ENABLED`, `NEXT_PUBLIC_ORGANIZATIONS_ENABLED` | Team and organization management |
| `ACCESS_CONTROL_ENABLED`, `NEXT_PUBLIC_ACCESS_CONTROL_ENABLED` | Permission groups |
| `SSO_ENABLED`, `NEXT_PUBLIC_SSO_ENABLED` | SAML and OIDC sign-in |
| `WHITELABELING_ENABLED`, `NEXT_PUBLIC_WHITELABELING_ENABLED` | Custom branding |
| `AUDIT_LOGS_ENABLED`, `NEXT_PUBLIC_AUDIT_LOGS_ENABLED` | Audit logging |
| `NEXT_PUBLIC_DATA_RETENTION_ENABLED` | Data retention configuration |
| `CREDENTIAL_SETS_ENABLED`, `NEXT_PUBLIC_CREDENTIAL_SETS_ENABLED` | Polling groups for email triggers |
| `INBOX_ENABLED`, `NEXT_PUBLIC_INBOX_ENABLED` | Sim Mailer inbox |
| `DISABLE_INVITATIONS`, `NEXT_PUBLIC_DISABLE_INVITATIONS` | Disable invitations; manage membership via Admin API |
### Organization Management
When billing is disabled, use the Admin API to manage organizations:
```bash
# Create an organization
curl -X POST https://your-instance/api/v1/admin/organizations \
-H "x-admin-key: YOUR_ADMIN_API_KEY" \
-H "Content-Type: application/json" \
-d '{"name": "My Organization", "ownerId": "user-id-here"}'
# Add a member
curl -X POST https://your-instance/api/v1/admin/organizations/{orgId}/members \
-H "x-admin-key: YOUR_ADMIN_API_KEY" \
-H "Content-Type: application/json" \
-d '{"userId": "user-id-here", "role": "admin"}'
```
### Workspace Members
When invitations are disabled, use the Admin API to manage workspace memberships directly:
```bash
# Add a user to a workspace
curl -X POST https://your-instance/api/v1/admin/workspaces/{workspaceId}/members \
-H "x-admin-key: YOUR_ADMIN_API_KEY" \
-H "Content-Type: application/json" \
-d '{"userId": "user-id-here", "permissions": "write"}'
# Remove a user from a workspace
curl -X DELETE "https://your-instance/api/v1/admin/workspaces/{workspaceId}/members?userId=user-id-here" \
-H "x-admin-key: YOUR_ADMIN_API_KEY"
```
### Notes
- Access Control is scoped per workspace. Set `ACCESS_CONTROL_ENABLED` and `NEXT_PUBLIC_ACCESS_CONTROL_ENABLED` to enable it on every workspace in a self-hosted deployment, bypassing the Enterprise plan check.
- When `DISABLE_INVITATIONS` is set, users cannot send invitations. Use the Admin API to manage workspace and organization memberships instead.
<FAQ items={[
{ question: "What are the minimum requirements to self-host Sim?", answer: "The Docker Compose production setup includes the Sim application (8 GB memory limit), a realtime collaboration server (1 GB memory limit), and a PostgreSQL database with pgvector. A machine with at least 16 GB of RAM and 4 CPU cores is recommended. You will also need Docker and Docker Compose installed." },
{ question: "Can I run Sim completely offline with local AI models?", answer: "Yes. Sim supports Ollama and VLLM for running local AI models. A separate Docker Compose configuration (docker-compose.ollama.yml) is available for deploying with Ollama. This lets you run workflows without any external API calls, keeping all data on your infrastructure." },
{ question: "How does data privacy work with self-hosted deployments?", answer: "When self-hosted, all data stays on your infrastructure. Workflow definitions, execution logs, credentials, and user data are stored in your PostgreSQL database. If you use local AI models through Ollama or VLLM, no data leaves your network. When using external AI providers, only the data sent in prompts goes to those providers." },
{ question: "Do I need a paid license to self-host Sim?", answer: "The core Sim platform is open source under Apache 2.0 and can be self-hosted for free. Enterprise features like SSO (SAML/OIDC), access control with permission groups, and organization management require an Enterprise subscription for production use. These features can be enabled via environment variables for development and evaluation without a license." },
{ question: "Which SSO providers are supported?", answer: "Sim supports SAML 2.0 and OIDC protocols, which means it works with virtually any enterprise identity provider including Okta, Azure AD (Entra ID), Google Workspace, and OneLogin. Configuration is done through Settings in the workspace UI." },
{ question: "How do I manage users when invitations are disabled?", answer: "Use the Admin API with your admin API key. You can create organizations, add members to organizations with specific roles, add users to workspaces with defined permissions, and remove users. All management is done through REST API calls authenticated with the x-admin-key header." },
{ question: "Can I scale Sim horizontally for high availability?", answer: "The Docker Compose setup is designed for single-node deployments. For production scaling, you can deploy on Kubernetes with multiple application replicas behind a load balancer. The database can be scaled independently using managed PostgreSQL services. Redis can be configured for session and cache management across multiple instances." },
{ question: "How do access control permission groups work?", answer: "Permission groups are created per workspace and let you restrict which AI providers, workflow blocks, and platform features are available to specific members of that workspace. Each user can belong to at most one group per workspace (and different groups in different workspaces). Users not assigned to any group have full access. Restrictions are enforced at both the UI level (hiding restricted options) and at execution time (blocking unauthorized operations) — execution enforcement is based on the workflow's workspace. Any workspace admin on an Enterprise-entitled workspace can manage permission groups." },
]} />
Once enabled, each feature is configured through the same Settings UI as Sim Cloud. When invitations are disabled, use the Admin API (`x-admin-key` header) to manage organization and workspace membership.

View File

@@ -62,16 +62,14 @@ The **Callback URL** shown in the form is the endpoint your identity provider mu
**OIDC providers** (Okta, Microsoft Entra ID, Google Workspace, Auth0):
```
https://simstudio.ai/api/auth/sso/callback/{provider-id}
https://sim.ai/api/auth/sso/callback/{provider-id}
```
**SAML providers** (ADFS, Shibboleth):
```
https://simstudio.ai/api/auth/sso/saml2/callback/{provider-id}
https://sim.ai/api/auth/sso/saml2/callback/{provider-id}
```
For self-hosted, replace `simstudio.ai` with your instance hostname.
### 5. Save and test
Click **Save**. To test, sign out and use the **Sign in with SSO** button on the login page. Enter an email address at your configured domain — Sim will redirect you to your identity provider.
@@ -92,7 +90,7 @@ Click **Save**. To test, sign out and use the **Sign in with SSO** button on the
2. Select **OIDC - OpenID Connect**, then **Web Application**
3. Set the **Sign-in redirect URI** to your Sim callback URL:
```
https://simstudio.ai/api/auth/sso/callback/okta
https://sim.ai/api/auth/sso/callback/okta
```
4. Under **Assignments**, grant access to the relevant users or groups
5. Copy the **Client ID** and **Client Secret** from the app's **General** tab
@@ -109,9 +107,7 @@ Click **Save**. To test, sign out and use the **Sign in with SSO** button on the
| Client ID | From Okta app |
| Client Secret | From Okta app |
<Callout type="info">
The issuer URL uses Okta's default authorization server (`/oauth2/default`), which is pre-configured on every Okta org. If you created a custom authorization server, replace `default` with your server name.
</Callout>
The issuer URL uses Okta's default authorization server, which is pre-configured on every Okta org. If you created a custom authorization server, replace `default` with your server name.
</Tab>
@@ -124,7 +120,7 @@ Click **Save**. To test, sign out and use the **Sign in with SSO** button on the
1. Go to **Microsoft Entra ID → App registrations → New registration**
2. Under **Redirect URI**, select **Web** and enter your Sim callback URL:
```
https://simstudio.ai/api/auth/sso/callback/azure-ad
https://sim.ai/api/auth/sso/callback/azure-ad
```
3. After registration, go to **Certificates & secrets → New client secret** and copy the value immediately — it won't be shown again
4. Go to **Overview** and copy the **Application (client) ID** and **Directory (tenant) ID**
@@ -140,10 +136,6 @@ Click **Save**. To test, sign out and use the **Sign in with SSO** button on the
| Client ID | Application (client) ID |
| Client Secret | Secret value |
<Callout type="info">
Replace `{tenant-id}` with your Directory (tenant) ID from the app's Overview page. Sim auto-discovers token and JWKS endpoints from the issuer.
</Callout>
</Tab>
<Tab value="Google Workspace">
@@ -156,7 +148,7 @@ Click **Save**. To test, sign out and use the **Sign in with SSO** button on the
2. Set the application type to **Web application**
3. Add your Sim callback URL to **Authorized redirect URIs**:
```
https://simstudio.ai/api/auth/sso/callback/google-workspace
https://sim.ai/api/auth/sso/callback/google-workspace
```
4. Copy the **Client ID** and **Client Secret**
@@ -187,14 +179,12 @@ Click **Save**. To test, sign out and use the **Sign in with SSO** button on the
2. Choose **Claims aware**, then **Enter data about the relying party manually**
3. Set the **Relying party identifier** (Entity ID) to your Sim base URL:
```
https://simstudio.ai
https://sim.ai
```
For self-hosted, use your instance's base URL (e.g. `https://sim.company.com`)
4. Add an endpoint: **SAML Assertion Consumer Service** (HTTP POST) with the URL:
```
https://simstudio.ai/api/auth/sso/saml2/callback/adfs
https://sim.ai/api/auth/sso/saml2/callback/adfs
```
For self-hosted: `https://sim.company.com/api/auth/sso/saml2/callback/adfs`
5. Export the **Token-signing certificate** from **Certificates**: right-click → **View Certificate → Details → Copy to File**, choose **Base-64 encoded X.509 (.CER)**. The `.cer` file is PEM-encoded — rename it to `.pem` before pasting its contents into Sim.
6. Note the **ADFS Federation Service endpoint URL** (e.g. `https://adfs.company.com/adfs/ls`)
@@ -204,7 +194,7 @@ Click **Save**. To test, sign out and use the **Sign in with SSO** button on the
|-------|-------|
| Provider Type | SAML |
| Provider ID | `adfs` |
| Issuer URL | `https://simstudio.ai` |
| Issuer URL | `https://sim.ai` |
| Domain | `company.com` |
| Entry Point URL | `https://adfs.company.com/adfs/ls` |
| Certificate | Contents of the `.pem` file |
@@ -223,7 +213,7 @@ Click **Save**. To test, sign out and use the **Sign in with SSO** button on the
Once SSO is configured, users with your domain (`company.com`) can sign in through your identity provider:
1. User goes to `simstudio.ai` and clicks **Sign in with SSO**
1. User goes to `sim.ai` and clicks **Sign in with SSO**
2. They enter their work email (e.g. `alice@company.com`)
3. Sim redirects them to your identity provider
4. After authenticating, they are returned to Sim and added to your organization automatically
@@ -235,10 +225,6 @@ Users who sign in via SSO for the first time are automatically provisioned and a
Password-based login remains available. Forcing all organization members to use SSO exclusively is not yet supported.
</Callout>
<Callout type="info">
**Self-hosted:** Automatic organization provisioning requires `ORGANIZATIONS_ENABLED=true` in addition to `SSO_ENABLED=true`. Without it, SSO authentication still works — users get a valid session — but they are not automatically added to an organization.
</Callout>
---
<FAQ items={[
@@ -268,7 +254,7 @@ Users who sign in via SSO for the first time are automatically provisioned and a
},
{
question: "What is the Callback URL?",
answer: "The Callback URL (also called Redirect URI or ACS URL) is the endpoint in Sim that receives the authentication response from your identity provider. For OIDC providers it follows the format: https://simstudio.ai/api/auth/sso/callback/{provider-id}. For SAML providers it is: https://simstudio.ai/api/auth/sso/saml2/callback/{provider-id}. You must register this URL in your identity provider before SSO will work."
answer: "The Callback URL (also called Redirect URI or ACS URL) is the endpoint in Sim that receives the authentication response from your identity provider. For OIDC providers it follows the format: https://sim.ai/api/auth/sso/callback/{provider-id}. For SAML providers it is: https://sim.ai/api/auth/sso/saml2/callback/{provider-id}. You must register this URL in your identity provider before SSO will work."
},
{
question: "How do I update or replace an existing SSO configuration?",

View File

@@ -3,7 +3,6 @@ title: Whitelabeling
description: Replace Sim branding with your own logo, colors, and links
---
import { Callout } from 'fumadocs-ui/components/callout'
import { FAQ } from '@/components/ui/faq'
import { Image } from '@/components/ui/image'
@@ -65,9 +64,7 @@ Whitelabeling replaces the following visual elements:
- **Primary and accent colors** — applied to buttons, active states, and highlights
- **Support and legal links** — help prompts and footer links point to your URLs
<Callout type="info">
Whitelabeling applies only to members of your organization. Public-facing pages (login, marketing) are not affected.
</Callout>
Whitelabeling applies only to members of your organization. Public-facing pages (login, marketing) are not affected.
---
@@ -103,4 +100,4 @@ WHITELABELING_ENABLED=true
NEXT_PUBLIC_WHITELABELING_ENABLED=true
```
Once enabled, configure branding through **Settings → Enterprise → Whitelabeling** the same way as Sim Cloud.
Once enabled, configure branding through **Settings → Enterprise → Whitelabeling** the same way.

View File

@@ -5,6 +5,9 @@
-- permission_group_member table also gains a denormalized workspace_id column
-- so the database can enforce the "one group per user per workspace"
-- invariant with a composite unique index.
--
-- Every statement below is written to be idempotent so the migration can be
-- safely re-run after a partial failure.
-- 0. Backfill workspace -> organization links for grandfathered workspaces whose
-- billed account user is the sole owner of exactly one organization. This is a
@@ -29,13 +32,42 @@ WHERE w."organization_id" IS NULL
AND w."billed_account_user_id" = owner_orgs."user_id";--> statement-breakpoint
-- 1. Add workspace_id columns as nullable so existing rows can coexist during the data migration.
ALTER TABLE "permission_group" ADD COLUMN "workspace_id" text;--> statement-breakpoint
ALTER TABLE "permission_group" ADD CONSTRAINT "permission_group_workspace_id_workspace_id_fk" FOREIGN KEY ("workspace_id") REFERENCES "public"."workspace"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "permission_group_member" ADD COLUMN "workspace_id" text;--> statement-breakpoint
ALTER TABLE "permission_group_member" ADD CONSTRAINT "permission_group_member_workspace_id_workspace_id_fk" FOREIGN KEY ("workspace_id") REFERENCES "public"."workspace"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "permission_group" ADD COLUMN IF NOT EXISTS "workspace_id" text;--> statement-breakpoint
DO $$ BEGIN
IF NOT EXISTS (
SELECT 1 FROM information_schema.table_constraints
WHERE constraint_name = 'permission_group_workspace_id_workspace_id_fk'
AND table_name = 'permission_group'
) THEN
ALTER TABLE "permission_group"
ADD CONSTRAINT "permission_group_workspace_id_workspace_id_fk"
FOREIGN KEY ("workspace_id") REFERENCES "public"."workspace"("id")
ON DELETE cascade ON UPDATE no action;
END IF;
END $$;--> statement-breakpoint
ALTER TABLE "permission_group_member" ADD COLUMN IF NOT EXISTS "workspace_id" text;--> statement-breakpoint
DO $$ BEGIN
IF NOT EXISTS (
SELECT 1 FROM information_schema.table_constraints
WHERE constraint_name = 'permission_group_member_workspace_id_workspace_id_fk'
AND table_name = 'permission_group_member'
) THEN
ALTER TABLE "permission_group_member"
ADD CONSTRAINT "permission_group_member_workspace_id_workspace_id_fk"
FOREIGN KEY ("workspace_id") REFERENCES "public"."workspace"("id")
ON DELETE cascade ON UPDATE no action;
END IF;
END $$;--> statement-breakpoint
-- 1b. Relax NOT NULL on permission_group.organization_id before the data migration.
-- Step 3 inserts clone rows with organization_id = NULL to mark them as the new
-- workspace-scoped shape. This DROP NOT NULL is a no-op if already nullable.
ALTER TABLE "permission_group" ALTER COLUMN "organization_id" DROP NOT NULL;--> statement-breakpoint
-- 2. Materialize a plan of (source permission group, target workspace, new clone id)
-- so we can insert the clone rows AND the member rows with stable references.
-- Temp tables are always fresh per transaction, so this is naturally idempotent.
CREATE TEMP TABLE "__permission_group_clone_plan" (
"source_id" text NOT NULL,
"cloned_id" text NOT NULL,
@@ -49,6 +81,8 @@ JOIN "workspace" w ON w."organization_id" = pg."organization_id"
WHERE pg."organization_id" IS NOT NULL;--> statement-breakpoint
-- 3. Create the workspace-scoped clone rows using the planned ids.
-- Naturally idempotent: after a successful prior run there are no org-scoped
-- rows left, so the clone plan is empty and this INSERT is a no-op.
INSERT INTO "permission_group" (
"id",
"workspace_id",
@@ -101,6 +135,7 @@ WHERE EXISTS (
);--> statement-breakpoint
-- 5. Delete legacy org-scoped rows now that clones exist.
-- Idempotent: no rows match on a re-run.
DELETE FROM "permission_group_member"
WHERE "permission_group_id" IN (
SELECT "id" FROM "permission_group" WHERE "organization_id" IS NOT NULL
@@ -109,20 +144,20 @@ WHERE "permission_group_id" IN (
DELETE FROM "permission_group" WHERE "organization_id" IS NOT NULL;--> statement-breakpoint
-- 6. Enforce NOT NULL on both workspace_id columns now that every surviving row has one.
-- SET NOT NULL is a no-op if already NOT NULL.
ALTER TABLE "permission_group" ALTER COLUMN "workspace_id" SET NOT NULL;--> statement-breakpoint
ALTER TABLE "permission_group_member" ALTER COLUMN "workspace_id" SET NOT NULL;--> statement-breakpoint
-- 7. Drop legacy structures and swap indexes.
ALTER TABLE "permission_group" DROP CONSTRAINT "permission_group_organization_id_organization_id_fk";--> statement-breakpoint
DROP INDEX "permission_group_org_name_unique";--> statement-breakpoint
DROP INDEX "permission_group_org_auto_add_unique";--> statement-breakpoint
DROP INDEX "permission_group_member_user_id_unique";--> statement-breakpoint
ALTER TABLE "permission_group" DROP COLUMN "organization_id";--> statement-breakpoint
CREATE UNIQUE INDEX "permission_group_workspace_name_unique" ON "permission_group" USING btree ("workspace_id","name");--> statement-breakpoint
CREATE UNIQUE INDEX "permission_group_workspace_auto_add_unique" ON "permission_group" USING btree ("workspace_id") WHERE auto_add_new_members = true;--> statement-breakpoint
CREATE UNIQUE INDEX "permission_group_member_group_user_unique" ON "permission_group_member" USING btree ("permission_group_id","user_id");--> statement-breakpoint
CREATE UNIQUE INDEX "permission_group_member_workspace_user_unique" ON "permission_group_member" USING btree ("workspace_id","user_id");--> statement-breakpoint
ALTER TABLE "permission_group" DROP CONSTRAINT IF EXISTS "permission_group_organization_id_organization_id_fk";--> statement-breakpoint
DROP INDEX IF EXISTS "permission_group_org_name_unique";--> statement-breakpoint
DROP INDEX IF EXISTS "permission_group_org_auto_add_unique";--> statement-breakpoint
DROP INDEX IF EXISTS "permission_group_member_user_id_unique";--> statement-breakpoint
ALTER TABLE "permission_group" DROP COLUMN IF EXISTS "organization_id";--> statement-breakpoint
CREATE UNIQUE INDEX IF NOT EXISTS "permission_group_workspace_name_unique" ON "permission_group" USING btree ("workspace_id","name");--> statement-breakpoint
CREATE UNIQUE INDEX IF NOT EXISTS "permission_group_workspace_auto_add_unique" ON "permission_group" USING btree ("workspace_id") WHERE auto_add_new_members = true;--> statement-breakpoint
CREATE UNIQUE INDEX IF NOT EXISTS "permission_group_member_group_user_unique" ON "permission_group_member" USING btree ("permission_group_id","user_id");--> statement-breakpoint
CREATE UNIQUE INDEX IF NOT EXISTS "permission_group_member_workspace_user_unique" ON "permission_group_member" USING btree ("workspace_id","user_id");--> statement-breakpoint
-- 8. Sweep any residual dead config keys from pre-existing workspace-scoped rows (if any).
UPDATE "permission_group" SET "config" = ("config" - 'hideEnvironmentTab' - 'hideTemplates') WHERE "config" ? 'hideEnvironmentTab' OR "config" ? 'hideTemplates';