Connect answers two different authorization questions, and it is worth being
precise about which is which:

- **May this workspace use this application at all?** Connect answers that
  itself — audience, verification, subscription state, sign-off. You read the
  answer from the readiness endpoint or the SDK's access gate.
- **May this member do this thing inside your product?** Connect *resolves* the
  answer and hands it to you. **It never enforces it.** Your backend does.

That second sentence is the whole contract. Connect does not know what
`campaign:publish` means, and it never checks it — most applications on the
platform are external products with their own datastores.

## Permissions

A permission is a capability your application can grant, declared in its own
catalogue and namespaced by the application key:

```
pulse_email:campaign:manage
pulse_email:template:read
pulse_email:domain:verify
```

The convention is `<key>:<resource>:<action>`. The `<key>:` prefix is added for
you; you declare the rest.

Permission ids are free-form after the prefix, with two reservations:
`platform:*` is refused, and so is any collision with a Connect built-in.

## Roles

A role is a named subset of your permission catalogue that a workspace can
assign to a member: `app:<key>:<roleKey>`.

**`admin` is reserved and always exists.** It is synthesised rather than
stored, and it resolves to your *entire* catalogue — including permissions you
add later. That last part matters: a customer's admin gets a new permission the
moment you declare it, with no action on their side.

Every other role is one you define. A role key is permanent for the same reason
a feature key is: renaming it orphans every member already assigned it.

## What arrives at sign-in

The token exchange carries the resolved list:

```ts
const session = await getConnectSession();

if (!session.permissions.includes("pulse_email:campaign:manage")) {
  return new Response("Forbidden", { status: 403 });
}
```

`granted` is already flattened — Connect has resolved the member's role, the
`admin` synthesis, and any custom workspace role scoped to your application,
and handed you the final list. You do not walk roles; you check permissions.

<Callout type="warn">
Check on the server, on every request that matters. The granted list is a
statement about what the user may do, not a guarantee that they only tried
those things. Hiding a button is a courtesy; the check behind it is the
control.
</Callout>

## What a workspace controls, and what you control

You declare the vocabulary. The workspace decides who gets what.

| | Declared by | Changed in |
| --- | --- | --- |
| Permission catalogue | you (the application) | Console → your application → Authorization |
| Roles beyond Admin | you (the application) | Console → your application → Authorization |
| Which member has which role | the customer | their workspace's Team page |

A workspace cannot invent a permission you have not declared, and a member's
grant can never exceed what the person granting it already holds.

## Deleting a permission

Deleting one removes it from every role that references it, in the same write —
there is no window in which a role points at a permission that no longer
exists. Members holding an affected role lose the capability the next time
their access is resolved, which is at their next sign-in, not immediately.

Plan for that gap. If a permission is being removed for a security reason
rather than a tidy-up, revoking sessions is the mechanism that takes effect
now.

## Not to be confused with

- **Features** — unlocked for a *subscription*, not a member. A member without
  `campaign:manage` on a plan that bundles `campaign_builder` sees a product
  that has the feature and a person who may not use it. Both checks are real
  and neither substitutes for the other. See
  [Capabilities](/docs/concepts/capabilities).
- **Connect's own platform permissions** — what a CosmoOps console operator can
  do. Nothing to do with your application's catalogue, which is why
  `platform:*` is a refused prefix.