# Permissions & Groups

Armature uses group-based RBAC. Permissions are granted to groups, and users
inherit the union of permissions from all groups they belong to. There are no
per-user permission grants — all access flows through group membership.

## Groups

### System groups

| Group | ID | Purpose |
|-------|----|---------|
| Everyone | `00000000-...0001` | Implicit membership for every authenticated user. Default permissions: `extension.use`, `workflow.submit`. |
| Admins | `00000000-...0002` | Full platform access. Members receive all seven permission slugs. Replaces the legacy `role = admin` check. |

Every new user is automatically added to **Everyone** on registration.
Admin status is granted by adding a user to the **Admins** group in
**Admin > People > Groups**.

### Custom groups

Administrators can create additional groups under **Admin > People > Groups**.
Each custom group has:

- **Name** — display label
- **Description** — purpose (shown in admin UI)
- **Scope** — always `global` (team-scoped groups reserved for future use)
- **Permissions** — zero or more permission slugs from the table below

## Permission slugs

Seven platform permissions control access to kernel features:

| Slug | Description |
|------|-------------|
| `surface.admin.access` | Full admin panel access (tabs, settings, package management) |
| `admin.view` | Read-only admin panel access (monitoring, health, audit log) |
| `extension.use` | Use installed extension surfaces and libraries |
| `extension.install` | Install, update, enable, and disable packages |
| `workflow.create` | Create and edit workflow definitions |
| `workflow.submit` | Submit instances to public-link workflows |
| `token.unlimited` | Bypass per-user token budgets (API rate limiting) |

Permissions follow a `domain.action` naming convention.

## Permission resolution

When a request arrives, the kernel resolves the effective permission set:

1. Fetch all groups the user belongs to (including Everyone).
2. Union all permission arrays across those groups.
3. Cache the result for the duration of the request.

A user has a permission if **any** of their groups grants it.

Frontend code checks permissions via `sw.can('slug')` — see the
[Frontend JS Guide](FRONTEND-JS-GUIDE) for details.

## Extension permissions

Separate from user permissions, each **package** can request sandbox
capabilities. These are granted per-package in **Admin > Packages**:

| Permission | Grants |
|------------|--------|
| `db.read` | Query `ext_data` tables (read-only) |
| `db.write` | Insert, update, and delete rows in `ext_data` tables |
| `api.http` | Make outbound HTTP requests from Starlark |
| `notifications.send` | Send in-app notifications to users |
| `secrets.read` | Read admin-configured extension secrets |
| `realtime.publish` | Publish WebSocket events to subscribed clients |
| `connections.read` | Read external connection configs (decrypted) |
| `workflow.access` | Read workflow definitions and instances |

See the [Starlark Reference](STARLARK-REFERENCE) for how these
map to sandbox modules.

## Settings cascade

Package settings use a three-tier resolution model:

```
user override → team override → global default
```

At each tier:

- **Global** — set by admins in **Admin > Packages > Settings**
- **Team** — set by team admins in **Team Admin > Settings**
- **User** — set by users in **Settings > Extensions**

### The `user_overridable` flag

Each setting key in a package manifest can declare `user_overridable`:

```json
{
  "settings": [
    { "key": "theme", "user_overridable": true },
    { "key": "api_endpoint", "user_overridable": false }
  ]
}
```

- `true` (default) — team and user scopes can override the global value.
- `false` — only the global (admin) value is used. Team and user values
  are silently ignored during resolution.

This gives administrators a lock mechanism: set `user_overridable: false`
on security-sensitive keys to prevent lower scopes from changing them,
while allowing cosmetic preferences to flow freely.

### Resolution algorithm

1. Start with the global value for each key.
2. For each key where `user_overridable` is true (or undeclared):
   - If a team-scoped value exists, it overrides global.
   - If a user-scoped value exists, it overrides team.
3. For keys where `user_overridable` is false:
   - Team and user values are discarded.
4. Unknown keys (not in schema) default to overridable.
