5.4 Role specs on mounts (read/write/delete/invoke)
Access control is configured per mount with an access object. The runtime
enforces it in the host (not in the service), uniformly for every service —
prebuilt or custom. (For the bigger picture — the operator/installer tiers and
the authority invariant — read 5.0 first.)
The shape
"access": {
"read": "all",
"write": "A E",
"delete": "A",
"invoke": "A E U"
}
Each key gates a class of HTTP methods by the action it represents:
| Key | Methods it gates | Default |
|---|---|---|
read |
GET, HEAD, OPTIONS | all |
write |
PUT, PATCH (idempotent upsert) | A |
delete |
DELETE | = write |
invoke |
POST and other non-idempotent verbs | = write |
So within an access object, an unset key defaults to world-readable and
admin-writable; you tighten or loosen from there. The split rests on HTTP's own
line: PUT/PATCH are an idempotent
upsert (write), while POST is the non-idempotent action verb (invoke) —
a store's keyless create, a pipeline run, an auth login. delete is its own key
because removal is the asymmetric, irreversible operation you most often want to
gate separately; it falls back to write when unset.
Shorthands.
accessmay also be the string"open"(anyone) or"authenticated"(any logged-in principal), instead of the object form.
Fail closed. A mount with no
accessat all is not reachable — every request is denied (401for an anonymous caller,403once authenticated). Omittingaccessis never an implicit "public"; a public mount must opt in explicitly with"access": "open". This way a forgotten policy fails safe instead of silently exposing the mount.
Role tokens
A role-spec value is one or more space-separated tokens:
all— anyone, including anonymous callers.authenticated— any logged-in principal, regardless of role.- a role name (
A,editor, …) — the principal must hold that role. - a role + path pattern — the role applies only under a path, with
substitution:
"U /user/{email}"lets aUprincipal act only under/user/<their-own-email>(the{email}is replaced with the caller's id). Other placeholders resolve from string claims named byauth.jwtUserProps, so"authenticated /{accountId}"scopes each caller to their account. A missing claim never matches.
How a decision is made
- The method maps to a key (
GET→read,PUT→write,DELETE→delete,POST→invoke). - The runtime checks the caller against that key's tokens.
- No principal + unsatisfied spec →
401. A principal lacking the required role →403. (The distinction tells a client whether to authenticate or give up.)
Path-scoped grants in practice
The path-pattern form expresses "users can edit their own record but not others'":
{ "path": "/data/users", "service": "data",
"config": { "access": {
"read": "A",
"write": "A, U /data/users/{email}"
} } }
An admin (A) can write anywhere under the mount; a plain user (U) can write
only to the record keyed by their own email. To go finer than whole-record —
restricting individual fields (e.g. let users edit their own record but not
their roles) — use the data service's fieldLevelAuthz with x-rs-read /
x-rs-write schema annotations (4.5).
Inline per-pipeline access
A pipeline mount authorizes its execution surface per spec. The mount's
access is the floor; a stored spec's optional inline access object
({ read?, write?, delete?, invoke? }) overrides it per key — looser or
tighter — so one mount can host a public login spec alongside an admin-only
one. POST→invoke here means "run this pipeline".
{ "pipeline": [ … ],
"access": { "invoke": "all" }, // this spec is public…
"description": "login" }
Because a spec's access defines authority, only an operator may set or
change it (5.0). A non-operator may freely author and edit a spec's logic, but
their write must leave the access field exactly as stored. See Part 7 for
authoring; manage-style keys do not exist — a spec is gated by the same four
actions as any mount.
Internal calls and trust
Pipeline steps and capability grants make internal calls. These are not trusted for being internal — the caller's principal propagates and the same role specs apply on the inner call. So a pipeline acting on behalf of a logged-in user can't be used to escalate past that user's permissions, and an anonymous trigger reaches only what an anonymous caller could. To deliberately cross a boundary, use the operator-configured elevation gateway (5.0, Part 7), which adds a configured role rather than dropping identity. Runtime-originated system calls (scheduler ticks) are trusted, since they exist only by operator config and can't be forged from the wire.
Next: letting browsers from other origins talk to your tenant — CORS.
← Previous: 5.3 Tokens, cookies, bearer · Manual home · Next: 5.5 CORS and the CSRF guard →