Authority
A kind and a reach. Because a rank can only say more or less, and the rules an operator actually has are not ranked.
Three roles are a rank, and a rank can only say more or less. The rules an operator actually has are not ranked:
- the person who runs the cluster has no business reading the records;
- the identity that ingests events writes, and must not be able to drop a table;
- and — the case this exists for — reading or writing inside a namespace must not confer creating and dropping databases in it.
No position on a ladder of three says any of those, because each one is some of a rung and none of the next.
So an authority is a pair: a kind, and the reach it is held over.
The five kinds
| kind | what it is |
|---|---|
read | read records |
write | write records |
manage | create and drop the container's children — databases in a namespace, tables in a database — and define structure on them |
govern | declare users and move authority around |
operate | topology, replicas and the backup file: running the thing rather than using it |
replicate | collect the log as a peer — store-wide only, never at namespace reach |
replicate is the one kind that cannot be granted at a narrower reach, and
the reason is what it would let somebody do. A subscription hands a machine the
log inside its reach, users and credential hashes included; a namespace owner who
could grant it would be able to admit a follower of their own choosing. So the
kind is store-wide or it is refused, and which records a peer actually collects
is decided by the REPLICATES clause on its membership row rather than by the
grant. See Adding a node.
Nothing in that table implies anything else in it. write over a namespace does
not confer manage over it, which is the sentence the whole model exists to make
true: writing records where databases live does not confer creating or dropping
them.
The three reaches
STORE, NAMESPACE <name>, DATABASE <namespace>.<name>. A kind held at a
container is held over everything inside it and over nothing outside — the one
implication in the model, and it runs downward only.
Every reach is led by a keyword, so that a table name can never be read as a
reach. STORE is a reserved word for exactly that reason: before it was
reserved, GRANT read ON store TO ada was a statement about the table named
store, and a reach spelled as a bare word would have silently widened it.
DEFINE TABLE store;
Refused at the token. That is what reserving it cost, and it is the cheaper half of the trade.
Saying it
A user can be declared with the set directly, instead of with a role:
DEFINE USER ops AUTHORITIES operate PASSWORD 'a long one';
DEFINE USER ingest ON prod.shop AUTHORITIES write PASSWORD 'a long one';
DEFINE USER nadia ON NAMESPACE prod AUTHORITIES manage PASSWORD 'a long one';
ops runs the node and reads no records. ingest writes into one database and
cannot define a table there. nadia creates and drops databases in prod and
reads nothing in them — the headline rule, in one statement.
Authority moves with the same two verbs a grant uses, over a reach instead of a table:
GRANT manage ON DATABASE prod.shop TO kim;
GRANT read ON NAMESPACE prod TO kim;
REVOKE manage ON DATABASE prod.shop FROM kim;
REVOKE operate ON STORE FROM kim;
A kind the language does not have is refused where it is written, and so is a reach that names nothing:
GRANT scribble ON STORE TO nobody;
NoSuchAuthority.
Nobody hands out what they do not hold
GRANT is refused when either half exceeds the granter's own — a wider kind, or
a wider reach. An owner of one database cannot mint an authority over the store,
and cannot move somebody sideways into a kind they do not hold themselves.
Without that rule every other rule on this page would be one statement away from being decoration: the escalation refused at the front door would simply walk in through the act of granting.
The three roles still mean what they meant
A role is one spelling of a set, and both spellings are kept.
| role | the set it stands for |
|---|---|
viewer | read |
editor | read, write, manage |
owner | read, write, manage, govern, operate |
Each at the reach the user was declared at.
That mapping is deliberately unchanged for users who already exist — including
editor, which bundles write with manage and is therefore exactly the
combination this model was built to make refusable. Narrowing it on an upgrade
would be an outage delivered as a migration. What the model changes is what can
now be said; it does not reach back and re-decide what was already promised.
A declared tenancy is asked first
The ON is not a default a grant overrides — it is the boundary the session is
inside, a second and independent confinement asked before the held set is
consulted. Otherwise ON would be decoration, and the escalation refused at the
declaring side would simply move to the granting side.
So GRANT read ON NAMESPACE staging TO nina, where nina was declared
ON NAMESPACE prod, is refused. The two tenancies miss each other, so the
holding could never be consulted — and a statement that returns ok and has no
effect is worse than either honest answer, because the operator's only evidence
that a grant landed is the statement not complaining, and the next person reads
the holding back out of INFO FOR USER and believes it.
The test is overlap in either direction, not containment in one.
manage ON STORE granted to a user of prod is perfectly usable inside prod,
since containment runs downward, and refusing it would be refusing a working
grant. REVOKE is not refused this way either: taking away a holding that could
never be used is exactly how one stored before this rule existed gets cleaned
up.
The confinement is enforced where a session selects a container, which means
USE is the first thing that has to succeed:
USE NAMESPACE staging;
OutsideTenancy, for a user of prod — before any table is named, and without
saying whether staging exists, because a refusal that answers that has answered
the question it declined.
Taking it away reaches a connection that is already open
This is the property a permission system is least often asked about and can least afford to get wrong, so it is stated as a number rather than as a reassurance.
| what | when it takes effect |
|---|---|
ALTER USER, DROP USER, REVOKE | that session's next statement |
| a running subscription | within one poll round — at most 250 ms |
Neither waits for the connection to close. A session re-reads its own user from the catalog on every statement, and a feed re-asks on every round, so a revocation does not stop at the edge of whoever happened to be connected when it was issued.
Reading who has what
Two questions, and the store answers both:
INFO FOR USER kim;
INFO FOR ACCESS TO TABLE orders;
The first is what does this person reach. The second is who reaches this object, which is the question an incident actually starts from, since the thing you are holding is the table.
Both answer from the same check the statements themselves pass through. The
second is worth saying out loud about: for every user, the store signs a
throwaway session in as them and puts a real USE, a real SELECT and a real
DELETE to the ordinary authorization path. It does not work reachability out
from grants and roles a second way — two opinions about one rule agree until they
do not, and when they stop nothing fails: the report simply becomes fiction, read
by the one person who has no way to check it.