Skip to content

Permission scopes

Internal (dashboard) authority is expressed as scopes derived mechanically from the database schema — entities, their foreign-key containment tree, and declared column facets. Nothing is hand-named: adding a table registers its scopes automatically at the next boot.

<root>.<resource-path>[.<facet>].<action>[:column op value]
  • Roots (3): org (tenant tree — Organization is recursive, so a sub-org is just org.org.*), user (the person’s own subtree: credentials, linked identities), platform (Axowl operator).
  • Resource path follows foreign-key containment: org.app_group.app.create. An org never contains a User — its only reach into a person is the membership badge (org.member.*) with issuance verbs (invite, suspend, revoke, role), never CRUD.
  • Facets are declared column groups that map 1:1 to UI cards: org.app_group.branding.update grants editing the Branding card only.
  • Actions come from each entity’s declared kind — Record → create/update/delete, Badge → issuance verbs, Edge → add/remove, Credential → issue/revoke, Log → read-only, Config → update — plus declared domain verbs (e.g. org.setting.rotate_signing_key).

Every scope belongs to exactly one of three layers, and wildcards never cross layers:

LayerGuardsEnforced at
viewPage entry (the door)route guard / nav
readData reads (the drawer)query endpoints
actionMutations (the stamp)mutation endpoints

Wildcards therefore carry a layer tag: org.*|view, org.*|read, org.*|action. An untagged .* does not exist. Holding org.app_group.*|action grants every mutation in that subtree — and no page access.

Grants are stored concrete (wildcards expand at grant-compile time). A permission check is a single indexed lookup — pure YES/NO — plus a clock compare: every entitlement carries ExpiresAt (null = permanent; a timestamp makes it a just-in-time temporary grant that lapses automatically).

Presets are computed subsets of the derived library (EntitlementPresets.For) — a single source shared by the seeder and new-org creation. System-protected resources (seals, signing keys, credentials, audit, sessions) are never delegable and never enter a preset.

RoleHolds
ORG_OWNER / REPRESENTATIVE_DIRECTORorg.*|view + org.*|read + org.*|action (all delegable)
ORG_ADMINsame three layer wildcards (billing refinement via deny-override)
ORG_TEAM_LEADMember set + badge issuance verbs (org.member.*) + reporting edges (org.member.relationship.*)
ORG_MEMBERorg.org.view + the whole org.app_group subtree (app work) + reporting view
ORG_FREELANCERsame as ORG_MEMBER (joins with a personal email)