> ## Documentation Index
> Fetch the complete documentation index at: https://docs.vana.org/llms.txt
> Use this file to discover all available pages before exploring further.

# Scope grammar

> How a grant's scope entries encode the operation a builder may perform - read by default, write with a prefix.

A [grant](/protocol-reference/grants-permissions) carries a list of scope entries. Each entry is `operation:scope`, where the operation prefix is optional. An entry with no prefix means read.

| Entry                 | Operation       | Scope           | What the grantee may do            |
| --------------------- | --------------- | --------------- | ---------------------------------- |
| `notes.entries`       | read (implicit) | `notes.entries` | read that scope                    |
| `write:notes.entries` | write           | `notes.entries` | write that scope, and nothing else |
| `write:chatgpt.*`     | write           | `chatgpt.*`     | write any chatgpt scope            |

Read and write never cross. This is a property of the encoding rather than a separate check: the read policy matches entries literally, so `write:x` can never satisfy a read of `x`, and the write policy only accepts entries that carry the write operation, so a plain entry can never satisfy a write.

## Why read is the implicit default

* Every grant issued before this convention is a read grant. Making read implicit keeps all of them valid with no migration and no change to the signed payload.
* It fails closed on clients that predate the grammar. An older Personal Server sees `write:notes.entries` as a scope name it does not hold, matches nothing, and denies. The failure mode of a client that does not understand a new operation is refusal, not accidental access.

## Matching rules

* The operation is matched exactly. Comparison is case sensitive and the token is lowercase ASCII.
* Wildcards apply to the scope part only, using the existing [scope matcher](/protocol-reference/scopes-schemas#scope-taxonomy). `write:chatgpt.*` is a wildcard over chatgpt scopes; there is no wildcard over operations.
* An entry whose operation is not recognised is ignored for authorization, which means it denies. This is what makes the grammar safe to extend.
* An entry is a single operation over a single scope. To grant read and write over the same scope, the grant carries two entries.

## Operations

| Operation | Status                      | Meaning                                                      |
| --------- | --------------------------- | ------------------------------------------------------------ |
| read      | implicit, never written out | read records under the scope                                 |
| `write:`  | implemented                 | write records under the scope, through the owner ingest path |
| `delete:` | reserved, not implemented   | reserved so that delete does not need a second convention    |

Operations that cannot be expressed as a single token do not belong in this grammar. Running a transformation over a user's data and returning a derivative is one of those, and it is expected to be modelled as its own request type rather than as a scope prefix. See [Operation-scoped grants](/protocol-reference/grants-permissions#operation-scoped-grants).

## Scope names

The prefix is the only part of the string this grammar defines. The scope name itself is declared by the [schema](/protocol-reference/scopes-schemas) and stays dot separated, source first, lowercase.

## What builders see

You should not construct or parse these strings by hand. The string form is a wire and storage detail: it is what the user signs and what the [DP RPC](/protocol-reference/dp-rpc) stores. The [Vana SDK](/applications/context-gateway#the-sdk) exposes a grant as scope and actions instead, and consent screens render a table of scope against what the app may do.

* Every grant the SDK reads back from the DP RPC (`getGrant`, `listGrantsByUser`) carries a derived `permissions` field: one `{ scope, actions }` row per scope pattern, with `actions` drawn from `"read"` and `"write"`. The signed `scopes` array is left untouched.
* `grantPermissions(scopes)` produces that grouped view from any scope list, and `permissionsToScopes(permissions)` is its inverse, so you can build a grant request from checkboxes without writing a prefix.
* `hasAction(scopes, scope, action)` answers "does this grant let me write `notes.entries`?" using the same matcher the Personal Server uses.
* `parseScopeEntry(entry)` and `formatScopeEntry({ scope, action })` handle a single entry when you need one.

```typescript theme={null}
import { grantPermissions, hasAction } from "@opendatalabs/vana-sdk";

const grant = await gateway.getGrant(grantId);
// grant.scopes      -> ["notes.entries", "write:notes.entries", "chatgpt.*"]
// grant.permissions -> [
//   { scope: "chatgpt.*", actions: ["read"] },
//   { scope: "notes.entries", actions: ["read", "write"] },
// ]

hasAction(grant.scopes, "notes.entries", "write"); // true
hasAction(grant.scopes, "chatgpt.conversations", "write"); // false
```

An entry the SDK does not recognise is never treated as read: `parseScopeEntry` and `grantPermissions` throw, `hasAction` skips it, and `permissions` is left absent on the grant so a consent screen cannot show such a grant as narrower than it is.

## Not covered here

* Fees, which may differ per operation.
* Delete semantics. The prefix is reserved, the behaviour is not specified yet.

## Related

* [Grants & permissions](/protocol-reference/grants-permissions) - the grant the entries live in
* [Scopes & schemas](/protocol-reference/scopes-schemas) - what a scope name is
* [Personal Servers](/protocol-reference/personal-servers) - where the entries are enforced
