Skip to main content

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": [ … ]
}
]
PropertyDescription
nameUnique across the entire tenant model. Basis of every identifier the definition produces.
grantToObjectsWhich objects carry the AC-id: one or more roots, each optionally spreading down a tree of traversals.
grantToUsersWhich 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[])​

FieldMeaning
classEvery instance of this class carries the AC-id (required).
nameOverrides this root's node-id path segment, for a second root on the same class. Optional; see Sibling nodes and name.
conditionPredicate over the object's own local fields, evaluated at write time. No relations, no aggregates, no clock — see Step 4.
operationsWhich 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.
traverseChild hops.

Traversal node (traverse[])​

FieldMeaning
relatedClassThe class to traverse to (required). See Choosing which relation to follow.
asClassThe 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.
nameOverrides this node's node-id path segment, for a sibling on the same relatedClass (+ asClass). Optional; see Sibling nodes and name.
conditionAs above, against the object this node reaches.
operationsWhich of WRITE/CREATE/DELETE. Required — unlike a root, there's no default.
traverseFurther child hops.

Role (roles[])​

"roles": [
{
"name": "purchaseManager",
"description": "Works with purchase orders; reads the shared master data.",
"accessControl": [
{ "definition": "masterData" },
{ "definition": "purchaseOrders", "operations": ["READ"] }
]
}
]
PropertyDescription
nameUnique tenant-wide. A user profile may hold any number; rights are the union.
descriptionFree text for whoever assigns roles. Not used as the option label in the profile form — that's the name.
accessControlWhich definitions this role pulls in. One entry per definition.
accessControl[].definitionName of the definition, as declared in the top-level accessControl array.
accessControl[].operationsOptional 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}"), or UUIDv5("{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's name if it has one and its class/relatedClass (+ @asClass) otherwise. No operation is baked in, so changing a node's operations never 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.