Skip to content

Roles & permissions

Axowl uses a snapshot-based RBAC model. Roles are templates; assigning a role to a member resolves its permission templates into a flat, sealed set of effective permissions.

Permission (catalog: "org.setting.update") seeded in SystemOrg (schema-derived)
RolePermissionTemplate (Role ↔ scope expression) per org
Role (ORG_OWNER / ORG_ADMIN / ORG_TEAM_LEAD /
ORG_MEMBER / ORG_FREELANCER / custom) per org, has Level
ConnectedIdRole (member ↔ role assignment) per org
▼ resolved by SnapshotService
ConnectedIdPermission (ResolvedScope, flattened) ← the O(1) check table
  • Permission is a catalog of what actions exist (Permission.cs). It carries the base scope only.
  • RolePermissionTemplate binds a permission to a role with an expression that may contain variables, e.g. server.create:region={region} (RolePermissionTemplate.cs).
  • Assigning a role runs SnapshotService.CreateSnapshotAsync (SnapshotService.cs:23), which injects variables and writes flat ConnectedIdPermission.ResolvedScope rows. Permission checks read only that table.
  • Roles are the single source of truth. MembershipType (Owner/Admin/Member) is derived from the member’s top Role.Level — never set independently. See Connected ID.

Every org is created with five preset roles, each carrying platform.* scopes (CreateOrganizationCommandHandler.CreateDefaultRoles):

RoleLevelScopes
ORG_OWNEROwnerplatform.*
ORG_ADMINAdminlayer-tagged wildcards org.*|view/read/action (billing via deny-override)
ORG_TEAM_LEADWriteplatform.{organization,application,user,settings,audit}.read
ORG_MEMBERReadplatform.{organization,application,user,settings,audit}.read
ORG_FREELANCERReadsame scopes as ORG_MEMBER, joins with a personal email

ORG_TEAM_LEAD starts from the same resource scopes as ORG_MEMBER; it differs by hierarchy Level (used for approval routing) and screen access. ORG_FREELANCER is a Member-equivalent for external contractors, selected via the invite’s Is Freelancer option. Legal entities (Corporation / SPC) also get a locked REPRESENTATIVE_DIRECTOR role at Owner level.

Custom roles are created in the dashboard and carry any subset of the catalog. See the full scope list in Permission scopes.

Snapshots are immutable point-in-time grants. Changing a role does not retroactively change already-issued snapshots unless the role is re-snapshotted (SnapshotService.ReSnapshotBatchAsync) — modeled on “a re-issued card”, preserving auditability.