Skip to content
autonym.dev
ContentsHide contents

Core

Scope grammar implementation

spec 1.0-draft-1

Status: implementation note for A6.2. It records how the reference library implements the section; its requirements bind that library and add none for other implementations. Document version 1.0-draft-1.

This implements the grammar in A6.2, not an authorisation server. An authorisation server parses the scope of an authorisation or token request with it; no recipient route accepts scopes. Scopes from a token MUST NOT replace the stored grant as authority (A6.2; read-path verification remains pending).

The source of truth is packages/schema/src/scope/index.ts. scopes.json publishes the closed purpose/capability sets and limits, with the purpose codes and capability meanings below; its scope marker, grammar-and-meanings, says that it holds the grammar and the meanings issuance applies (A6.2), not an endpoint; scope-token.json validates one token; examples are generated and round-trip through the parser. A token schema does not validate a complete grant. Complete sets MUST pass the semantic checks below.

Parsing and canonical form

parseScopes(string) returns a canonical token array; serialiseScopes(array) revalidates and joins it. Both MUST reject unknown tokens, trailing content, invalid case and malformed boundaries with invalid_scope. No input is trimmed, decoded or case-folded. The separator is exactly one ASCII space. Input is limited to 16,384 ASCII bytes, 128 tokens before deduplication and 128 characters per domain. Non-ASCII input, leading/trailing spaces and all other whitespace are rejected. The domain character alphabet is the published scope grammar, including extension names and exact case. A syntactically valid domain is not automatically a registered vocabulary domain.

Exactly one purpose token MUST occur before deduplication. A valid token set without one fails with purpose_missing; two occurrences, even identical ones, fail with invalid_scope. An empty string also fails with purpose_missing. Malformed input is rejected before the purpose count. All ten purposes and eight capability strings are generated from schemas.

* MUST appear only on L0 read scopes. No missing domain implies an L0 floor. ceilingFor(set, domain) returns the explicit domain ceiling, or L0 for a wildcard, or null. It revalidates the whole set and rejects a wildcard or invalid queried domain. It is a ceiling lookup only: expiry, status, read counters, confidence, purpose, declared-only and every other stored grant restriction are still required before a read.

Read duplicates for the same exact domain and the same declared-only mode MUST collapse to their highest level at the first occurrence's position. Duplicate capabilities collapse to their first occurrence. Explicit domains and * remain distinct. Mixed unrestricted and :declared reads for the same domain MUST be rejected in this implementation. At grant issuance, any accepted :declared modifier makes the entire stored grant declared_only:true, including wildcard/explicit-domain combinations. Consent shows this restriction. A person wanting different declaration restrictions uses separate grants. Explicit domain ceilings take precedence over the wildcard L0 ceiling; no declaration restriction is removed. No present code treats a ceiling as permission to disclose an entry.

Validation errors contain a fixed name, registered code and title, never the input or the underlying Zod issues. invalid_scope and purpose_missing are registered as status 400 Problem Details variants for routes outside OAuth. At the authorisation and token endpoints, a record-grant scope the parser refuses fails OAuth invalid_scope (RFC 6749 sections 4.1.2.1 and 5.2) in the OAuth error form, and so does a scope without a purpose. The A6.6 lifecycle scope is not a record-grant scope: the token endpoint checks it on its own, outside the parser. The parser itself throws ScopeValidationError.

Purpose codes

Where a purpose has to be an absolute URI, such as PDPP's purpose_code (B1.4), the purpose autonym:purpose:<category> is written urn:autonym:purpose:<category>: urn:autonym:purpose:booking, for example. scopes.json lists the ten codes as purpose_codes. No other code names an Open Personal Context purpose.

Capability meanings

Each capability scope has one meaning at issuance (A6.2), published in scopes.json as capability_meanings. A client receives a third-party capability only when its class may hold it (A2).

Scope Who may hold it Meaning
autonym:propose Third party Requests a contribution grant, separate from any read grant
autonym:confirm Third party Sets allow_confirmation on that contribution grant
autonym:persist Third party Permits persist_until on a read grant
autonym:delegate Third party Sets allow_delegation on a read grant
autonym:confirm:safety First party Reserved to the person's own first-party clients; never issued to a recipient
autonym:write:declared First party Reserved to the person's own first-party clients; never issued to a recipient
autonym:log:read First party Reserved to the person's own first-party clients; never issued to a recipient
autonym:manage First party Reserved to the person's own first-party clients; never issued to a recipient

A capability of one grant kind cannot be combined with a request for the other kind, so autonym:propose with a read scope, autonym:persist or autonym:delegate is a mixed request. The second of the published examples, which reads two domains and asks for autonym:propose, is valid grammar but such a mixed request, so issuance refuses it with invalid_scope. openid and offline_access are not Open Personal Context scopes: the parser refuses them like any other unknown token.

Advertising purpose

autonym:purpose:advertising is separate from personalisation and recommendation. The parser MUST preserve that distinction and MUST NOT add read, proposal or persistence capabilities from a purpose alone. A combined advertising and personalisation set MUST be rejected by the one-purpose rule; separate consent requires separate grants. SCOPE-10 checks this grammar boundary. A6.7 defines the draft consent requirements. Parsing a scope does not establish consent, an eligible domain or permission to disclose.

Executable checks

packages/schema/test/scopes.test.ts supplies SCOPE-01 (source fixture), SCOPE-02 (closed purposes/capabilities), SCOPE-03 (one purpose), SCOPE-04 (wildcard and absence), SCOPE-05 (duplicates and declaration ambiguity), SCOPE-06 (exact parsing), SCOPE-07 (limits and caller boundary validation), SCOPE-08 (10,000 arbitrary inputs) and SCOPE-09 (10,000 valid sets, round trips and ceiling bounds). SCOPE-10 checks the advertising boundary. These are grammar checks, not certification of the A17 recipient capabilities.

Recipient OpenAPI/MCP contracts are now generated, while endpoints and SDK implementations remain pending. Parsing is not an endpoint or a conformance claim. The separate client-lifecycle scope in A6 is not a record-grant scope.

References to other standards and projects describe intended technical alignment only and do not imply endorsement or partnership.