Schema reference
Every field of an accessControl definition and a role, in one place. See Access Control for what they mean and how to build a model up from them.
Throughout: the AC-id is the identifier a definition mints, carried by both the objects it reaches and the users it grants to. Access is a non-empty overlap between the two sets.
Two top-level branches
accessControl and roles are separate arrays on a class file, not nested in one another. Definitions say which objects and users carry an identifier; roles say which definitions a user's profile pulls in. Both are aggregated tenant-wide at model load, so they need not sit on the same class — in practice they do, on one kind: CONFIG class.
{
"version": 1,
"class": "my-project.accessControl",
"kind": "CONFIG",
"accessControl": [
{ "name": "purchaseOrders", "grantToObjects": [ … ], "grantToUsers": [ … ] }
],
"roles": [
{ "name": "purchaseManager", "accessControl": [ { "definition": "purchaseOrders" } ] }
]
}
Note the name collision, which trips people up on first reading: the top-level accessControl holds definitions, while accessControl inside a role is that role's list of references to them. See Role.
Both are listed among the class file's top-level properties.
Definition (accessControl[])
"accessControl": [
{
"name": "hrDossier",
"grantToObjects": [ … ],
"grantToUsers": [ … ]
}
]
| Property | Description |
|---|---|
name | Unique across the entire tenant model. Basis of every identifier the definition produces. |
grantToObjects | Which objects carry the AC-id: one or more roots, each optionally spreading down a tree of traversals. |
grantToUsers | Which users carry it, on top of holding a role that references the definition (Step 6). A single ordered path from the anchor to a user-identifying type (typically commons.person). Omitted means path-less; present — even as [] — anchors the definition. |
Root (grantToObjects[])
| Field | Meaning |
|---|---|
class | Every instance of this class carries the AC-id (required). |
name | Overrides this root's node-id path segment, for a second root on the same class. Optional; see Sibling nodes and name. |
condition | Predicate over the object's own local fields, evaluated at write time. No relations, no aggregates, no clock — see Step 4. |
operations | Which of WRITE/CREATE/DELETE these objects are eligible for. READ follows from the root existing — see Step 5. Optional; omit to grant none of the other three. |
traverse | Child hops. |
Traversal node (traverse[])
| Field | Meaning |
|---|---|
relatedClass | The class to traverse to (required). See Choosing which relation to follow. |
asClass | The class the parent is linked under, as the far side declares it. Needed to reach a relation declared on a role, and to disambiguate a hop that runs against the declared direction — see Reaching a relation declared on a role. |
name | Overrides this node's node-id path segment, for a sibling on the same relatedClass (+ asClass). Optional; see Sibling nodes and name. |
condition | As above, against the object this node reaches. |
operations | Which of WRITE/CREATE/DELETE. Required — unlike a root, there's no default. |
traverse | Further child hops. |
Role (roles[])
"roles": [
{
"name": "purchaseManager",
"description": "Works with purchase orders; reads the shared master data.",
"accessControl": [
{ "definition": "masterData" },
{ "definition": "purchaseOrders", "operations": ["READ"] }
]
}
]
| Property | Description |
|---|---|
name | Unique tenant-wide. A user profile may hold any number; rights are the union. |
description | Free text for whoever assigns roles. Not used as the option label in the profile form — that's the name. |
accessControl | Which definitions this role pulls in. One entry per definition. |
accessControl[].definition | Name of the definition, as declared in the top-level accessControl array. |
accessControl[].operations | Optional narrowing. Omit unless this role should get less than the definition allows — see Step 7. |
Identifier derivation
Both identifiers are derived from name, deterministically, so the object side and the user side agree without a lookup.
- AC-id —
UUIDv5("{tenantId}:{name}"), orUUIDv5("{tenantId}:{name}:{anchorId}")for an anchored definition. - Node-id —
UUIDv5("{tenantId}:{name}:{path}"), one per root or node, where each path segment is the node'snameif it has one and itsclass/relatedClass(+@asClass) otherwise. No operation is baked in, so changing a node'soperationsnever changes a stored identifier.
The tenant is in both derivations even though every row is already tenant-scoped by column — cheap defence in depth.
Because name feeds both, renaming or removing a definition or a node orphans every row it produced. Any change to accessControl or roles needs a rebuild before it takes effect.
Namespace boundary
grantToObjects and grantToUsers are bound by the same system-namespace rule as ordinary relations: neither relatedClass nor asClass may reference a system.* type, at any depth. A user path stays inside the tenant model and ends on a tenant type; turning that into a platform account is a lookup outside the path grammar.