Skip to content

Commit 75adcd3

Browse files
committed
Merge branch 'staging' into improvement/access-control-ws-scoped
2 parents 8d3e0a6 + 699bbfd commit 75adcd3

755 files changed

Lines changed: 43777 additions & 24037 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.claude/rules/global.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
# Global Standards
22

33
## Logging
4-
Import `createLogger` from `sim/logger`. Use `logger.info`, `logger.warn`, `logger.error` instead of `console.log`.
4+
Import `createLogger` from `@sim/logger`. Use `logger.info`, `logger.warn`, `logger.error` instead of `console.log`. Inside API routes wrapped with `withRouteHandler`, loggers automatically include the request ID.
5+
6+
## API Route Handlers
7+
All API route handlers must be wrapped with `withRouteHandler` from `@/lib/core/utils/with-route-handler`. Never export a bare `async function GET/POST/...` — always use `export const METHOD = withRouteHandler(...)`.
58

69
## Comments
710
Use TSDoc for documentation. No `====` separators. No non-TSDoc comments.

.claude/rules/sim-testing.md

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -217,13 +217,20 @@ it('reads a row', async () => {
217217
```
218218

219219
**Default chains supported:**
220-
- `select()/selectDistinct()/selectDistinctOn() → from() → where()/innerJoin()/leftJoin() → where() → limit()/orderBy()/returning()/groupBy()`
220+
- `select()/selectDistinct()/selectDistinctOn() → from() → where()/innerJoin()/leftJoin() → where() → limit()/orderBy()/returning()/groupBy()/for()`
221221
- `insert() → values() → returning()/onConflictDoUpdate()/onConflictDoNothing()`
222-
- `update() → set() → where() → limit()/orderBy()/returning()`
223-
- `delete() → where() → limit()/orderBy()/returning()`
222+
- `update() → set() → where() → limit()/orderBy()/returning()/for()`
223+
- `delete() → where() → limit()/orderBy()/returning()/for()`
224224
- `db.execute()` resolves `[]`
225225
- `db.transaction(cb)` calls cb with `dbChainMock.db`
226226

227+
`.for('update')` (Postgres row-level locking) is supported on `where`
228+
builders. It returns a thenable with `.limit` / `.orderBy` / `.returning` /
229+
`.groupBy` attached, so both `await .where().for('update')` (terminal) and
230+
`await .where().for('update').limit(1)` (chained) work. Override the terminal
231+
result with `dbChainMockFns.for.mockResolvedValueOnce([...])`; for the chained
232+
form, mock the downstream terminal (e.g. `dbChainMockFns.limit.mockResolvedValueOnce([...])`).
233+
227234
All terminals default to `Promise.resolve([])`. Override per-test with `dbChainMockFns.<terminal>.mockResolvedValueOnce(...)`.
228235

229236
Use `resetDbChainMock()` in `beforeEach` only when tests replace wiring with `.mockReturnValue` / `.mockResolvedValue` (permanent). Tests using only `...Once` variants don't need it.

CLAUDE.md

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@ You are a professional software engineer. All code must follow best practices: a
44

55
## Global Standards
66

7-
- **Logging**: Import `createLogger` from `@sim/logger`. Use `logger.info`, `logger.warn`, `logger.error` instead of `console.log`
7+
- **Logging**: Import `createLogger` from `@sim/logger`. Use `logger.info`, `logger.warn`, `logger.error` instead of `console.log`. Inside API routes wrapped with `withRouteHandler`, loggers automatically include the request ID — no manual `withMetadata({ requestId })` needed
8+
- **API Route Handlers**: All API route handlers (`GET`, `POST`, `PUT`, `DELETE`, `PATCH`) must be wrapped with `withRouteHandler` from `@/lib/core/utils/with-route-handler`. This provides request ID tracking, automatic error logging for 4xx/5xx responses, and unhandled error catching. See "API Route Pattern" section below
89
- **Comments**: Use TSDoc for documentation. No `====` separators. No non-TSDoc comments
910
- **Styling**: Never update global styles. Keep all styling local to components
1011
- **ID Generation**: Never use `crypto.randomUUID()`, `nanoid`, or `uuid` package. Use `generateId()` (UUID v4) or `generateShortId()` (compact) from `@sim/utils/id`
@@ -93,6 +94,41 @@ export function Component({ requiredProp, optionalProp = false }: ComponentProps
9394

9495
Extract when: 50+ lines, used in 2+ files, or has own state/logic. Keep inline when: < 10 lines, single use, purely presentational.
9596

97+
## API Route Pattern
98+
99+
Every API route handler must be wrapped with `withRouteHandler`. This sets up `AsyncLocalStorage`-based request context so all loggers in the request lifecycle automatically include the request ID.
100+
101+
```typescript
102+
import { createLogger } from '@sim/logger'
103+
import type { NextRequest } from 'next/server'
104+
import { NextResponse } from 'next/server'
105+
import { withRouteHandler } from '@/lib/core/utils/with-route-handler'
106+
107+
const logger = createLogger('MyAPI')
108+
109+
// Simple route
110+
export const GET = withRouteHandler(async (request: NextRequest) => {
111+
logger.info('Handling request') // automatically includes {requestId=...}
112+
return NextResponse.json({ ok: true })
113+
})
114+
115+
// Route with params
116+
export const DELETE = withRouteHandler(async (
117+
request: NextRequest,
118+
{ params }: { params: Promise<{ id: string }> }
119+
) => {
120+
const { id } = await params
121+
return NextResponse.json({ deleted: id })
122+
})
123+
124+
// Composing with other middleware (withRouteHandler wraps the outermost layer)
125+
export const POST = withRouteHandler(withAdminAuth(async (request) => {
126+
return NextResponse.json({ ok: true })
127+
}))
128+
```
129+
130+
Never export a bare `async function GET/POST/...` — always use `export const METHOD = withRouteHandler(...)`.
131+
96132
## Hooks
97133

98134
```typescript
Lines changed: 216 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,216 @@
1+
---
2+
title: Access Control
3+
description: Restrict which models, blocks, and platform features each group of users can access
4+
---
5+
6+
import { Callout } from 'fumadocs-ui/components/callout'
7+
import { FAQ } from '@/components/ui/faq'
8+
import { Image } from '@/components/ui/image'
9+
10+
Access Control lets workspace admins define permission groups that restrict what each set of workspace members can do — which AI model providers they can use, which workflow blocks they can place, and which platform features are visible to them. Permission groups are scoped to a single workspace: a user can be in different groups (or no group) in different workspaces. Restrictions are enforced both in the workflow executor and in Mothership, based on the workflow's workspace.
11+
12+
---
13+
14+
## How it works
15+
16+
Access control is built around **permission groups**. Each group belongs to a specific workspace and has a name, an optional description, and a configuration that defines what its members can and cannot do. A user can belong to at most one permission group **per workspace**, but can belong to different groups in different workspaces.
17+
18+
When a user runs a workflow or uses Mothership, Sim reads their group's configuration and applies it:
19+
20+
- **In the executor:** If a workflow uses a disallowed block type or model provider, execution halts immediately with an error. This applies to both manual runs and scheduled or API-triggered deployments.
21+
- **In Mothership:** Disallowed blocks are filtered out of the block list so they cannot be added to a workflow. Disallowed tool types (MCP, custom tools, skills) are skipped if Mothership attempts to use them.
22+
23+
---
24+
25+
## Setup
26+
27+
### 1. Open Access Control settings
28+
29+
Go to **Settings → Enterprise → Access Control** in the workspace you want to manage. Each workspace has its own set of permission groups.
30+
31+
<Image src="/static/enterprise/access-control-groups.png" alt="Access Control settings showing a list of permission groups: Contractors, Sales, Engineering, and Marketing, each with Details and Delete actions" width={900} height={500} />
32+
33+
### 2. Create a permission group
34+
35+
Click **+ Create** and enter a name (required) and optional description. You can also enable **Auto-add new members** — when active, any new member who joins this workspace is automatically added to this group. Only one group per workspace can have this setting enabled at a time.
36+
37+
### 3. Configure permissions
38+
39+
Click **Details** on a group, then open **Configure Permissions**. There are three tabs.
40+
41+
#### Model Providers
42+
43+
Controls which AI model providers members of this group can use.
44+
45+
<Image src="/static/enterprise/access-control-model-providers.png" alt="Model Providers tab showing a grid of AI providers including Ollama, vLLM, OpenAI, Anthropic, Google, Azure OpenAI, and others with checkboxes to allow or restrict access" width={900} height={500} /> The list shows all providers available in Sim.
46+
47+
- **All checked (default):** All providers are allowed.
48+
- **Subset checked:** Only the selected providers are allowed. Any workflow block or agent using a provider not on the list will fail at execution time.
49+
50+
#### Blocks
51+
52+
Controls which workflow blocks members can place and execute.
53+
54+
<Image src="/static/enterprise/access-control-blocks.png" alt="Blocks tab showing Core Blocks (Agent, API, Condition, Function, Knowledge, etc.) and Tools (integrations like 1Password, A2A, Ahrefs, Airtable, and more) with checkboxes to allow or restrict each" width={900} height={500} /> Blocks are split into two sections: **Core Blocks** (Agent, API, Condition, Function, etc.) and **Tools** (all integration blocks).
55+
56+
- **All checked (default):** All blocks are allowed.
57+
- **Subset checked:** Only the selected blocks are allowed. Workflows that already contain a disallowed block will fail when run — they are not automatically modified.
58+
59+
<Callout type="info">
60+
The `start_trigger` block (the entry point of every workflow) is always allowed and cannot be restricted.
61+
</Callout>
62+
63+
#### Platform
64+
65+
Controls visibility of platform features and modules.
66+
67+
<Image src="/static/enterprise/access-control-platform.png" alt="Platform tab showing feature toggles grouped by category: Sidebar (Knowledge Base, Tables, Templates), Workflow Panel (Copilot), Settings Tabs, Tools, Deploy Tabs, Features, Logs, and Collaboration" width={900} height={500} /> Each checkbox maps to a specific feature; checking it hides or disables that feature for group members.
68+
69+
**Sidebar**
70+
71+
| Feature | Effect when checked |
72+
|---------|-------------------|
73+
| Knowledge Base | Hides the Knowledge Base section from the sidebar |
74+
| Tables | Hides the Tables section from the sidebar |
75+
| Templates | Hides the Templates section from the sidebar |
76+
77+
**Workflow Panel**
78+
79+
| Feature | Effect when checked |
80+
|---------|-------------------|
81+
| Copilot | Hides the Copilot panel inside the workflow editor |
82+
83+
**Settings Tabs**
84+
85+
| Feature | Effect when checked |
86+
|---------|-------------------|
87+
| Integrations | Hides the Integrations tab in Settings |
88+
| Secrets | Hides the Secrets tab in Settings |
89+
| API Keys | Hides the Sim Keys tab in Settings |
90+
| Files | Hides the Files tab in Settings |
91+
92+
**Tools**
93+
94+
| Feature | Effect when checked |
95+
|---------|-------------------|
96+
| MCP Tools | Disables the use of MCP tools in workflows and agents |
97+
| Custom Tools | Disables the use of custom tools in workflows and agents |
98+
| Skills | Disables the use of Sim Skills in workflows and agents |
99+
100+
**Deploy Tabs**
101+
102+
| Feature | Effect when checked |
103+
|---------|-------------------|
104+
| API | Hides the API deployment tab |
105+
| MCP | Hides the MCP deployment tab |
106+
| A2A | Hides the A2A deployment tab |
107+
| Chat | Hides the Chat deployment tab |
108+
| Template | Hides the Template deployment tab |
109+
110+
**Features**
111+
112+
| Feature | Effect when checked |
113+
|---------|-------------------|
114+
| Sim Mailer | Hides the Sim Mailer (Inbox) feature |
115+
| Public API | Disables public API access for deployed workflows |
116+
117+
**Logs**
118+
119+
| Feature | Effect when checked |
120+
|---------|-------------------|
121+
| Trace Spans | Hides trace span details in execution logs |
122+
123+
**Collaboration**
124+
125+
| Feature | Effect when checked |
126+
|---------|-------------------|
127+
| Invitations | Disables the ability to invite new members to the workspace |
128+
129+
### 4. Add members
130+
131+
Open the group's **Details** view and add members by searching for users by name or email. Only users who already have workspace-level access can be added. A user can only belong to one group per workspace — adding a user to a new group within the same workspace removes them from their current group for that workspace.
132+
133+
---
134+
135+
## Enforcement
136+
137+
### Workflow execution
138+
139+
Restrictions are enforced at the point of execution, not at save time. If a group's configuration changes after a workflow is built:
140+
141+
- **Block restrictions:** Any workflow run that reaches a disallowed block halts immediately with an error. The workflow is not modified — only execution is blocked.
142+
- **Model provider restrictions:** Any block or agent that uses a disallowed provider halts immediately with an error.
143+
- **Tool restrictions (MCP, custom tools, skills):** Agents that use a disallowed tool type halt immediately with an error.
144+
145+
This applies regardless of how the workflow is triggered — manually, via API, via schedule, or via webhook.
146+
147+
### Mothership
148+
149+
When a user opens Mothership, their permission group is read before any block or tool suggestions are made:
150+
151+
- Blocks not in the allowed list are filtered out of the block picker entirely — they do not appear as options.
152+
- If Mothership generates a workflow step that would use a disallowed tool (MCP, custom, or skills), that step is skipped and the reason is noted.
153+
154+
---
155+
156+
## User membership rules
157+
158+
- A user can belong to **at most one** permission group **per workspace**, but may be in different groups across different workspaces.
159+
- Moving a user to a new group within a workspace automatically removes them from their previous group in that workspace.
160+
- Users not assigned to any group in a workspace have no restrictions applied in that workspace (all blocks, providers, and features are available to them there).
161+
- If **Auto-add new members** is enabled on a group, new members of that workspace are automatically placed in the group. Only one group per workspace can have this setting active.
162+
163+
---
164+
165+
<FAQ items={[
166+
{
167+
question: "Who can create and manage permission groups?",
168+
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."
169+
},
170+
{
171+
question: "What happens to a workflow that was built before a block was restricted?",
172+
answer: "The workflow is not modified — it still exists and can be edited. However, any run that reaches a disallowed block will halt immediately with an error. The block must be removed or the user's group configuration must be updated before the workflow can run successfully."
173+
},
174+
{
175+
question: "Can a user be in multiple permission groups?",
176+
answer: "A user can belong to at most one permission group per workspace, but can belong to different groups in different workspaces. Adding a user to a new group within the same workspace automatically removes them from their previous group in that workspace."
177+
},
178+
{
179+
question: "What does a user see if they have no permission group assigned in a workspace?",
180+
answer: "Users with no group in a given workspace have no restrictions in that workspace. All blocks, model providers, and platform features are fully available to them there. Restrictions only apply in the specific workspaces where they are assigned to a group."
181+
},
182+
{
183+
question: "Does Mothership respect the same restrictions as the executor?",
184+
answer: "Yes. Mothership reads the user's permission group for the active workspace before suggesting blocks or tools. Disallowed blocks are filtered out of the block picker, and disallowed tool types are skipped during workflow generation."
185+
},
186+
{
187+
question: "Can I restrict access to specific workflows or workspaces?",
188+
answer: "Access Control operates at the feature and block level within a workspace. To restrict who can access the workspace itself, use workspace invitations and permissions. To apply different restrictions to different workflows, put them in different workspaces with distinct permission groups."
189+
},
190+
{
191+
question: "What is Auto-add new members?",
192+
answer: "When a group has Auto-add new members enabled, any new member who joins the workspace is automatically added to that group. Only one group per workspace can have this setting enabled at a time."
193+
}
194+
]} />
195+
196+
---
197+
198+
## Self-hosted setup
199+
200+
Self-hosted deployments use environment variables instead of the billing/plan check.
201+
202+
### Environment variables
203+
204+
```bash
205+
ACCESS_CONTROL_ENABLED=true
206+
NEXT_PUBLIC_ACCESS_CONTROL_ENABLED=true
207+
```
208+
209+
You can also set a server-level block allowlist using the `ALLOWED_INTEGRATIONS` environment variable. This is applied as an additional constraint on top of any permission group configuration — a block must be allowed by both the environment allowlist and the user's group to be usable.
210+
211+
```bash
212+
# Only these block types are available across the entire instance
213+
ALLOWED_INTEGRATIONS=slack,gmail,agent,function,condition
214+
```
215+
216+
Once enabled, permission groups are managed through **Settings → Enterprise → Access Control** the same way as Sim Cloud.

0 commit comments

Comments
 (0)