Concepts
Claims and permissions
The P W S R flags, Domain Claims, Event Broker and State Broker claims, how permissions resolve on a topic, and least-privilege recipes.
An App Key can do nothing on its own. What it may do comes from the claims linked to it: at most one Domain Claim, one Event Broker claim and one State Broker claim. Claims are reusable: link the same claim to every key that plays the same role.
Flags#
Every permission is a bitmask of four flags, with the same meaning everywhere:
| Flag | Value | Name | Grants |
|---|---|---|---|
P | 8 | Publish | Push data that notifies others: publish events; set, merge, delete, echo states. |
W | 4 | Write | Change or delete stored things: purge event history; manage keys, claims, configs. |
S | 2 | Subscribe | Receive live notifications (sub). |
R | 1 | Read | Query: history, states, configs, tell. |
Combine them by adding: P S R is 8 + 2 + 1 = 11; all four is 15; 0 denies. Flags never imply
each other: S without R receives live data but cannot read history, and P without S can
publish but not listen.
import { PUBLISH, WRITE, SUBSCRIBE, READ, combine, has } from '@thuum/client';
const flags = combine(PUBLISH, SUBSCRIBE, READ); // 11
has(flags, SUBSCRIBE); // true
has(flags, WRITE); // falseDomain Claims: management#
A Domain Claim grants W and/or R per protocol:
| Protocol | R lets the key… | W lets the key… |
|---|---|---|
domain | list App Keys and Domain Claims | create and delete App Keys; create and edit Domain Claims |
event | list Event Broker claims and the key picker | create, edit and delete Event Broker claims, link keys |
state | list State Broker claims and the key picker | create, edit and delete State Broker claims, link keys |
Only W and R apply (values 0, 1, 4, 5). Keys that only move data need no Domain Claim
at all.
Broker claims: data#
Event Broker and State Broker claims have the same shape:
| Field | Scope | Flags | Gates |
|---|---|---|---|
topic_flags | the whole Domain | W R | Topic configs: retention (Event Broker), TTL and size (State Broker). |
topics | one Main Topic each, and its family | P W S R | Data on that Main Topic and all its sub-topics. |
fallback_data_flags | every Main Topic not in topics | P W S R | Data everywhere else. 0 denies. |
What each data flag allows:
| Flag | Event Broker | State Broker |
|---|---|---|
P | publish (WebSocket pub, REST POST) | set, merge, del, echo (WebSocket and REST) |
W | purge history (REST DELETE) | not used |
S | subscribe (sub) | subscribe to changes (sub) |
R | read history (REST GET) | read (REST GET, WebSocket tell) |
Resolution#
For a topic, Thuum takes its Main Topic and looks it up in the claim's topics. If it is there,
those flags apply; otherwise fallback_data_flags applies. Flags are never combined across claims
or rows.
claim: topics { chat: 11 } fallback_data_flags: 1
topic: chat:support:42 → main topic "chat" → 11 (P S R)
topic: orders:1042 → main topic "orders" → 1 (R, the fallback)The client does the same computation, for display or to disable a button before a round trip:
import { resolveDataFlags, has, SUBSCRIBE } from '@thuum/client';
const me = await acme.me();
const { flags, source } = resolveDataFlags(me.event_claim!, 'chat:lobby');
const canListen = has(flags, SUBSCRIBE);REST requests resolve permissions on every request, so a claim change applies immediately. A WebSocket resolves them once, when it opens.
Recipes#
| Role | Claim |
|---|---|
| Browser in a chat | Event Broker: chat = P S R (11). Fallback 0. |
| Browser presence | State Broker: presence = P S R (11). Fallback 0. |
| Live dashboard (read-only) | Event Broker or State Broker: orders = S R (3). Fallback 0. |
| Backend publisher | Event Broker: orders = P (8). Fallback 0. |
| Backend with cleanup duties | Event Broker: orders = P W R (13), topic flags W R (5). |
| Operations console for one team | Domain Claim: event = R (1). Event Broker: fallback S R (3). |
| Full access (development only) | Domain Claim: domain, event, state = W R (5). Broker claims: topic flags 5, fallback 15. |
Linking rules#
- A key links to 0 or 1 claim of each kind. Linking a key that already has another claim of that
kind fails with
409: unlink it first. - A claim cannot be deleted while keys are linked to it (
409). - Unlinking is idempotent, and so is linking a key to the claim it already has.
Who sees what you can do#
GET /api/{domain}/me returns the caller's key, token expiry and linked claims, so an app can show
or hide features without probing for 403s. See Authentication.