Skip to main content
Access is decided by grants. There is one grant table and one shape, and every case your product has reduces to it:
(this holder) may (act) on (that resource).
The fourth row is the one that earns the design. A grant whose resource is another person makes “ask about someone else” an ordinary entitlement, checked by the same code as everything else — instead of a relationship table with its own rules that has to be audited separately.

Resources are a tree

You name the kinds. A syllabus is a programme with weeks beneath it and days beneath those; a CRM is accounts; a school is departments and cohorts.
A grant with inherit set covers the whole subtree, so enrolling somebody in a programme reaches every week and day inside it without enumerating them. A node optionally carries a body of knowledge; a week that carries none simply costs nothing. Cycles are refused when the tree is rebuilt, not tolerated. A loop in a parent chain does not return a wrong answer — it hangs the request that walks it.

Roles include, they do not rank

A role reaches the grants of the roles it includes:
A grant made to tutor is reachable by head_teacher. A grant made to head_teacher is not reachable by tutor.
A numeric level was the obvious alternative and is worse: it is a second ordering that can disagree with the first, and “may a head of year read a bursar’s ledger?” is not a question about magnitude. A lattice says exactly what it means, and it expresses a CRM’s manager ⊃ rep with the same construct as a school’s four levels.

Scoping a role to one subtree

One tutor role serves every tutor. Narrow the assignment, not the role:
The role’s grants are intersected with that subtree, so Ravi reaches his own cohort and nobody else’s, and adding a tutor needs no new grants at all.

Deny always wins

A grant can be an allow or a deny, and deny is subtracted last. That lets you exclude one person from one week without unpicking the role that everybody else depends on.

How a request is resolved

  1. The roles the principal holds, unexpired.
  2. Everything those roles reach through includes.
  3. Every grant held directly or by one of those roles.
  4. Subtrees expanded for inheriting grants.
  5. Role-scoped assignments intersected with their scope.
  6. Deny subtracted.
  7. What remains becomes the knowledge and the people this request may reach.
Resolution is cached for five minutes against a version that every entitlement write bumps — so a revoked grant takes effect immediately rather than when a cache expires.

What retrieval does with it

No end user

Nothing changes. The dashboard and the public widget behave exactly as they did before permissions existed.

An end user

Search is narrowed to what a grant names, in SQL, before ranking.
Filtering happens before ranking, not after. Filtering afterwards would mean paying to embed and rank rows the asker may not see — and, worse, would make “is it in the results” depend on how well it happened to match.

Knowledge that belongs to no group

A workspace’s profile, its FAQs and its hand-typed cards belong to no group, and by default they answer for everyone. That is right for an assistant talking to the public and wrong for a learner. So it is a per-workspace policy:
The default is tenant because that is what every existing workspace already does, and flipping it silently would take live assistants quiet. If your product has end users who must not see each other’s material, set strict deliberately — and expect some answers that used to work to stop.

It fails closed

An entitlement lookup that cannot be resolved yields an empty scope, not an open one. A request that the platform knows belongs to an end user but cannot identify reaches nothing.
This is the opposite of how workspace scoping behaves, and the difference is intentional. A background job with no workspace pinned is trusted code and reads across the platform. An end user is by definition untrusted, and the cost of guessing wrong is showing one person another person’s records.

Tools are stricter than knowledge

A role grants tool access explicitly, and an empty list means no tools. Knowledge keeps working when nobody has configured anything, because the cost of being wrong is a thinner answer. A tool can move money or change somebody’s record, so the default there is silence.