Overview
Graph access control decides which records in the knowledge graph a given person may see. It works by computing, for each person, the set of records they are entitled to, and then adding that set as a filter to every graph query made on their behalf. A person who is entitled to nothing sees nothing; there is no partial or best-effort answer. Policy lives in Postgres, in the Graph Authorization tables. It is deliberately not seeded: every deployment’s org structure differs, so a new install starts empty and an administrator enters the policy that matches their own graph. There are two surfaces, and they do different jobs:
This page is the order to create the rows in.
The four things a policy is made of
A working policy needs all four. Three of them on their own grant nobody anything.
Setting up a new deployment
1
Create the containment axes
In Staff admin > Graph Authorization > Roll-up axes, add one row per containment relationship in your
ontology — the edges that mean “this thing contains that thing”.For each axis set:
- Name — anything descriptive; it is only used for display and ordering.
- Edge element id — the relationship type, exactly as it appears in the graph.
- Direction —
downfollows the arrow,upwalks it in reverse. This is mechanical, not semantic: it describes how your graph happens to store the edge, not whether the axis means “upward” in the org chart.
2
Create the baselines
In Graph Authorization > Baselines (OWD), add one row per label in your graph. Set default access to:
privatefor labels whose records are access-controlled;public_readfor labels everyone may read.
3
Attach each containment axis to a baseline
Open each baseline that should inherit down a hierarchy and set its roll-up axis.This is the step that turns an axis into a containment axis, and it is the one with no visible symptom when
it is missed — see An axis you forgot to attach below.
4
Create the grant rules
In Graph Authorization > Grant rules, add the edges that attach a person to the graph. There are two
kinds, and they are not interchangeable — the difference is where the edge comes from:
Until at least one grant rule exists, a completely correct hierarchy still resolves to nothing for everyone,
because nothing connects a person to a starting point.
What each kind gives a person
Team rules work bottom-up. A person reaches the work they are attached to, and the containment axes then carry that outward — see the engagement a project belongs to, the client that engagement is for, and the other projects under it. On the reference deployment one consultant is directly attached to 146 projects and can see 313; the other 167 arrive purely through that fan-out.Membership rules work top-down. A person placed at a division reaches everything filed under it, including work they were never staffed on. That is the only way to give access to somebody with no work edges at all — a new joiner before their first project, or a leader who oversees rather than delivers.A policy built only from team rules is a legitimate and tighter configuration. Its cost, stated plainly: a person with no work edges sees nothing, and there is no rule that changes that.Optional: the management roll-up
If you want managers to inherit what their reports can see, add one further roll-up axis for the management edge — and do not attach it to any baseline. An axis with no baseline pointing at it is what the compiler treats as the management axis. Its direction must beup: the edge is stored report → manager, so the roll-up walks it in reverse.
Traps
An axis you forgot to attach
Skipping step 3 does not raise an error and does not empty anything. It reclassifies the axis: because it is attached to no baseline, the compiler reads it as the management axis instead. There is no warning to read, because nothing recorded what the axis was intended for. Symptom: records do not inherit down the hierarchy, and the management roll-up behaves oddly. Check: every axis you meant as containment is named by some baseline’s roll-up axis field.Two unattached axes
Only one management axis is supported. When there is more than one, the compiler picks the one whose name sorts first alphabetically.A management axis pointing down
A management axis with direction down is not evaluated, and every manager silently stops inheriting their
reports’ scope.
This one is especially hard to spot from the outside, because the compiled policy still reports the management
edge as IS_MANAGED_BY afterwards — that is a built-in default filling the hole, not your axis being
honoured. The field that actually says whether the roll-up is on is include report scopes.
Checking your work
Use Admin > Graph > Access control > Preview as user to check what a named person can see before you switch enforcement on, and the Diagnostics tab for what the compiler dropped. Two results are worth treating as failures even though neither raises an error:- Everyone sees the same thing. Scoping is not doing anything — usually a missing baseline or a missing axis attachment.
- Everyone sees nothing. Usually a missing grant rule, so nobody has a starting point.
Seeing the filter that was applied
When a superuser runs a graph query from chat, the tool result carries the query the filter actually produced, in two forms:authz_rewritten_query— what ran, with the access filter still held as a parameter.authz_executable_query— the same thing with the values filled in, so it can be pasted into a console and run as-is.
Both are shown to superusers only, and are absent — not blank — for everyone else. The filled-in form
lists every record the person is entitled to, so it describes their access in full.If the list is too long to show, you get a message saying how many entries were left out rather than a
shortened query. A cut-down filter is still valid and still runs — it just quietly answers a narrower
question, which is worse than showing nothing.
Records nobody can reach
Diagnostics > unreachable records counts records that no one can reach, by working out every person’s access and combining it. A record appears there because nothing in the graph connects it to anybody — not because the policy is wrong. The fix is upstream, in the data: an enrichment rule that gives those records the missing relationship. Widening the policy to cover them is the wrong instrument — it grants far more than the records in question.Related
- Ontology — the labels and relationship types this policy refers to
- Graph backend (Neo4j & FalkorDB)
- Enrichment rules — how to connect records that no relationship reaches