Connection scopes

A connection can do exactly what its scopes say and nothing else. Your Worker enforces the row; the client is a client.

The default is every declared read

Connecting an environment is where you narrow it, the control plane capability is what enforces it, and auth is the capability whose scopes are most worth reading twice.

Plus the seam’s own two.

That default exists because of a specific failure: a connection holding only the seam’s scopes opens a dashboard where every pane says the credential does not cover this call. That looks like a broken product rather than like a grant nobody made, and the fix people reach for is granting everything.

So connecting produces a management client that can actually read, without a second command.

It is derived, never listed

Each capability declares its admin routes with the scope each one needs, and connect reads that off the Worker it is registering — the same declaration the manifest serves.

So a capability you add is offered on your next connect with no coordination and nothing to keep in step, and a capability you do not compose is never mentioned. There is no list in the CLI that could drift from the routes.

A read is a route, not a name

This is the rule that makes the default safe:

A scope joins the default only when every declared route requiring it is a GET.

Scope matching is exact — no prefixes, no wildcards — so holding a scope confers every route that requires it. One mutating route anywhere makes the whole scope a write, however it is spelled.

A scope that sounds like a read and gates one write is a write. The derivation does not read names.

One scope stays in the default despite gating writes: the key-rotation scope, which covers a key listing and two key writes. It stays because it always has, and because dropping it would break rotation on every new connection — but nothing the derivation adds can write.

Narrowing it

At a terminal, connect lists every operation your Worker exposes, described in each capability’s own words, preselected to that default. Narrowing is the point of showing you the list.

Headlessly, name what you want:

pithy dashboard connect --env prod --scope manifest:read --scope auth:users:read

The flag is repeatable, and the raw arguments are read so several survive.

An explicitly empty selection is passed through as empty, not collapsed into the default. Somebody who deselected everything must not be handed a scope anyway.

On an --update, passing no scope means leave the grant alone rather than reset it.

Why so many scopes rather than one admin flag

Because reading a user and revoking their sessions are different kinds of act.

Reading a user is a privacy operation. Revoking their sessions is an availability one. A support tool that looks people up should never also be able to sign the whole customer base out, and an incident-response tool that kills a stolen session has no business reading every address in the user table.

Auth alone declares five, for exactly that reason.

What a narrowed grant does

Refuses every call it left out with controlplane/insufficient_scope.

And the manifest tells a client which routes those are before it tries — so a well-built management client shows a pane as unavailable rather than showing a button that fails.

Reads are audited too

Every control-plane call lands in your audit trail under its own actor kind, reads included.

Reading the user table hands a management client every customer’s email address. If only the writes were recorded, the trail would show one revoked session and say nothing about the customer list walked on the way there.

Changing your mind

--update re-points scopes on an existing connection without rebuilding it. Narrowing takes effect immediately, because your Worker reads the row on every call.

There is no temporarily grant this — a scope is on or off, and the audit trail is what tells you what was done with it.

ESC