Permission & notification schemes

Iskue decides who may do what through three stacked layers: global roles, per-project roles, and a per-project permission scheme of granular grants. A parallel notification scheme decides who is told when something happens on a ticket. Both kinds of scheme are defined once, org-wide, by a global administrator, and assigned to projects individually.

How the layers stack

Global roles (ADMIN, MEMBER, GUEST) live on the user account; a global ADMIN bypasses every project gate and every scheme check. Project roles (ADMIN, MEMBER, VIEWER) live on the project membership row and drive the coarse gates: any member may read, members other than VIEWER may write peripheral project content, and only a project ADMIN may administer the project. The permission scheme then decides granular, per-action rights on top of those gates.

StepCheckOutcome
1Global roleA global ADMIN passes unconditionally.
2Organisation isolationA principal outside the project's organisation is denied. No grant type can override this — not even ANYONE_LOGGED_IN.
3Scheme resolutionThe project's assigned scheme is used, else the scheme flagged is_default. If neither exists, the check denies.
4Restricted-ticket gateWhen the ticket in scope is restricted, every grant is blocked unless the principal is its reporter, its assignee or a project ADMIN.
5Grant matchingAllow if any grant for the requested permission key matches the principal; otherwise deny.

Many write paths run a membership gate *before* the scheme check — adding a comment, for instance, first requires a non-VIEWER project membership. A generous grant such as ANYONE_LOGGED_IN therefore widens access only within what the surrounding gates allow. On paths that check only the scheme (ticket creation is one), it extends to any member of the project's organisation — and never beyond it.

Permission schemes

A scheme is a named set of grants; each grant maps one permission key to one grantee. Schemes live in the permission_scheme and permission_grant tables (Citus reference tables on a cluster). Scheme names are unique case-insensitively, at most 120 characters, with an optional 500-character description. A project points at exactly one scheme via project.permission_scheme_id.

Grantee typeParameterMatches
PROJECT_ROLErole nameA built-in name (ADMIN/MEMBER/VIEWER) matches the user's membership role only; a custom role name matches the per-project custom-role assignment.
USERuser idThat specific user.
GROUPgroup idEvery member of the user group — group membership alone confers the grant.
REPORTER—The reporter of the ticket in context.
ASSIGNEE—The current assignee of the ticket in context.
ANYONE_LOGGED_IN—Any authenticated user, still subject to organisation isolation.

REPORTER and ASSIGNEE grants take effect only when a ticket is in scope. Project-level checks — creating a ticket, managing sprints, versions or components — carry no ticket context, so those two grants never match there.

Permission keys

KeyGrants the right to
BROWSE_PROJECTView the project and its tickets.
CREATE_TICKETCreate new tickets in the project.
EDIT_TICKETEdit ticket fields, rank, and release links (also required to resolve someone else's comment).
DELETE_TICKETPermanently delete tickets.
TRANSITION_TICKETMove tickets through the workflow.
ASSIGN_TICKETChange a ticket's assignee or co-assignees.
ASSIGNABLEBe set as a ticket's assignee (evaluated for the target user, not the actor).
ADD_COMMENTComment on tickets.
EDIT_OWN_COMMENTEdit comments you authored.
EDIT_ALL_COMMENTSEdit any comment.
DELETE_OWN_COMMENTDelete comments you authored.
DELETE_ALL_COMMENTSDelete any comment, including contact-authored portal comments.
LINK_TICKETCreate and remove issue links.
LOG_WORKLog work and edit or delete your own worklogs.
EDIT_ALL_WORKLOGSEdit anyone's worklog.
DELETE_ALL_WORKLOGSDelete anyone's worklog.
MANAGE_SPRINTSCreate, edit, start and complete sprints.
MANAGE_VERSIONSCreate, edit and release versions.
MANAGE_COMPONENTSCreate and edit components.
ADMINISTER_PROJECTManage project configuration and settings.

Three catalogue keys are not yet enforced in the current code: comment editing is author-only regardless of EDIT_OWN_COMMENT/EDIT_ALL_COMMENTS grants, and project administration is gated by the built-in project ADMIN membership role, not by ADMINISTER_PROJECT. Do not rely on granting or revoking these three to change behaviour. Note also that most read endpoints require project membership; BROWSE_PROJECT is additionally checked on content reads such as worklogs, votes, estimation, checklists, presence and favourites.

Defaults when no scheme is assigned

Resolution is: project.permission_scheme_id if set, otherwise the scheme flagged is_default — the seeded Default Permission Scheme (fixed id 11111111-1111-1111-1111-111111111111). If neither exists, every check denies for everyone except global admins. Migration V55__permission_schemes.sql backfills every existing project to the default scheme, and its grants reproduce the legacy project-role rules exactly, so an upgrade changes nothing; the worklog keys are seeded by V57__reconcile_worklog_time_tracking.sql, mirroring the corresponding comment grants.

Permission key(s)Default grantees
BROWSE_PROJECTRoles ADMIN, MEMBER, VIEWER
CREATE_TICKET, EDIT_TICKET, TRANSITION_TICKET, ASSIGN_TICKETRoles ADMIN, MEMBER
ADD_COMMENT, EDIT_OWN_COMMENT, DELETE_OWN_COMMENT, LINK_TICKET, MANAGE_SPRINTS, LOG_WORKRoles ADMIN, MEMBER
DELETE_TICKET, EDIT_ALL_COMMENTS, DELETE_ALL_COMMENTS, EDIT_ALL_WORKLOGS, DELETE_ALL_WORKLOGSRole ADMIN
MANAGE_VERSIONS, MANAGE_COMPONENTS, ADMINISTER_PROJECTRole ADMIN
ASSIGNABLEANYONE_LOGGED_IN

ASSIGNABLE is evaluated for the user *being assigned*, not for the actor. Under the default grant, any authenticated user in the project's organisation may be set as assignee; global admins are always assignable; users from another organisation never are.

Custom roles

A custom role is an org-admin-defined role *name* usable in PROJECT_ROLE grants alongside the built-ins. Definitions are global (unique name, at most 60 characters, optional 255-character description); assignment is per project and per user, and leaves the built-in membership role untouched — coarse access still comes from ADMIN/MEMBER/VIEWER, while custom roles add scheme-driven rights on top. Creating a custom role with a built-in name is rejected, and a built-in name in a grant only ever matches the membership role, so a custom role cannot shadow MEMBER.

Custom-role management
# Define a role (global admin; authentication omitted)
curl -X POST "$ISKUE_URL/api/v1/admin/custom-roles" \
  -H 'Content-Type: application/json' \
  -d '{"name":"Release Manager","description":"May manage releases"}'

# Assign it to a user in one project (project admin)
curl -X PUT "$ISKUE_URL/api/v1/projects/PROJ/custom-roles/<roleId>/members/<userId>"

# Remove the assignment
curl -X DELETE "$ISKUE_URL/api/v1/projects/PROJ/custom-roles/<roleId>/members/<userId>"

Deleting a custom role cascades its per-project assignments; grants that still name it simply stop matching anyone — the same as a role with no members. The /admin/permission-schemes page hosts the custom-role manager alongside the scheme editor, and the grant editor's role dropdown lists built-in roles first, then custom roles.

User groups

Groups feed GROUP grants and GROUP notification rules. user_group holds the definition (unique name, at most 120 characters) and group_membership the user–group pairs. Management is global-admin only under /api/v1/admin/groups: create, rename (PATCH /{id}), add a member (POST /{id}/members with {"userId":"..."}), remove a member (DELETE /{id}/members/{userId}), delete. On the permission side, a GROUP grant applies to every member of the group; membership alone confers the right, with organisation isolation still applied.

The permission-scheme editor

/admin/permission-schemes (global ADMIN only) lists schemes, creates new ones, edits grants and deletes unused schemes. A newly created scheme starts as a copy of the default scheme's grants, so editing begins from the safe baseline. Grant editing is replace-all: the editor submits the complete grant list. GET /api/v1/admin/permission-schemes/catalog supplies the permission keys, grantee types and role names the editor offers.

Scheme management API
# Create a scheme — it starts as a copy of the default scheme's grants
curl -X POST "$ISKUE_URL/api/v1/admin/permission-schemes" \
  -H 'Content-Type: application/json' \
  -d '{"name":"Locked down","description":"Members read, admins write"}'

# Replace the scheme's grants (replace-all semantics)
curl -X PUT "$ISKUE_URL/api/v1/admin/permission-schemes/<schemeId>/grants" \
  -H 'Content-Type: application/json' \
  -d '{
    "grants": [
      {"permissionKey":"BROWSE_PROJECT","granteeType":"PROJECT_ROLE","granteeParam":"VIEWER"},
      {"permissionKey":"BROWSE_PROJECT","granteeType":"PROJECT_ROLE","granteeParam":"MEMBER"},
      {"permissionKey":"BROWSE_PROJECT","granteeType":"PROJECT_ROLE","granteeParam":"ADMIN"},
      {"permissionKey":"EDIT_TICKET","granteeType":"PROJECT_ROLE","granteeParam":"Release Manager"},
      {"permissionKey":"ADD_COMMENT","granteeType":"REPORTER"},
      {"permissionKey":"ASSIGNABLE","granteeType":"ANYONE_LOGGED_IN"}
    ]
  }'

Validation on save: a PROJECT_ROLE grant needs a built-in or existing custom role name, USER an existing user id, GROUP an existing group id; REPORTER, ASSIGNEE and ANYONE_LOGGED_IN carry no parameter. Duplicate grants are silently de-duplicated. The default scheme cannot be deleted, nor can any scheme still assigned to a project — reassign those projects first. Every create, rename (PATCH /{id}), grant change and delete is recorded in the configuration audit log.

Assigning a scheme to a project

Project-side assignment — also available under project settings in the UI
GET /api/v1/projects/{projectKey}/permission-scheme          # active scheme (any project member)
GET /api/v1/projects/{projectKey}/permission-scheme/options  # assignable schemes (project admin)
PUT /api/v1/projects/{projectKey}/permission-scheme          # body {"schemeId":"..."} (project admin)

Notification schemes

A notification scheme maps ticket events to recipients. On each event, the project's scheme is consulted; every matching rule contributes recipients, and the union — de-duplicated, with the acting user removed — is notified. Schemes live in notification_scheme and notification_rule, are managed at /admin/notification-schemes (global ADMIN), and are assigned per project exactly like permission schemes via /api/v1/projects/{projectKey}/notification-scheme (plus /options, PUT to assign). Resolution defaults are identical: the project's notification_scheme_id, else the seeded Default Notification Scheme (fixed id 22222222-2222-2222-2222-222222222222); with neither, scheme-resolved recipients are simply none.

EventMeaningDefault recipients
CREATEDTicket createdNone — emitted with no default rule; opt in per scheme.
UPDATEDTicket updatedNone — emitted with no default rule; opt in per scheme.
COMMENTEDComment addedReporter, current assignee, watchers.
STATUS_CHANGEDStatus changedReporter, current assignee, watchers.
ASSIGNEDAssignee changedCurrent assignee.
MENTIONEDMentionedThe @mentioned users — intrinsic to the event and always notified regardless of scheme rules.
SLA_BREACHEDSLA breachedCurrent assignee.
Recipient typeParameterResolves to
CURRENT_ASSIGNEE—The ticket's current assignee.
REPORTER—The ticket's reporter.
WATCHERS—Everyone watching the ticket.
PROJECT_ROLEprojectRoleProject members holding that built-in role.
SPECIFIC_USERuserIdThat user — only if they are a project member.
GROUPgroupIdGroup members who are also project members.
ALL_PROJECT_MEMBERS—Every member of the project.

Notification PROJECT_ROLE rules accept only the built-in ADMIN/MEMBER/VIEWER — custom roles work in permission grants but not in notification rules. SPECIFIC_USER and GROUP rules are filtered to project members, so a rule cannot leak ticket activity to someone without project visibility.

How the channels are routed

Every resolved recipient always gets an in-app notification row. Email depends on the recipient's own email mode: IMMEDIATE sends one email per event (active accounts only), DAILY folds events into a single daily digest, and NONE stays in-app only. Two per-user switches silence both channels outright: a mute (notification_pref) may target one project, one event type, or both — a null side means "all" — and suppresses matching events entirely, mentions included; an active snooze suppresses everything until its timestamp expires.

Notification-scheme rules API
# Replace a scheme's rules (replace-all semantics)
curl -X PUT "$ISKUE_URL/api/v1/admin/notification-schemes/<schemeId>/rules" \
  -H 'Content-Type: application/json' \
  -d '{
    "rules": [
      {"eventType":"COMMENTED","recipientType":"REPORTER"},
      {"eventType":"COMMENTED","recipientType":"WATCHERS"},
      {"eventType":"CREATED","recipientType":"PROJECT_ROLE","projectRole":"ADMIN"},
      {"eventType":"STATUS_CHANGED","recipientType":"GROUP","groupId":"<groupId>"},
      {"eventType":"SLA_BREACHED","recipientType":"SPECIFIC_USER","userId":"<userId>"}
    ]
  }'

The same guard rails apply as for permission schemes: GET /api/v1/admin/notification-schemes/catalog lists events, recipient types and roles; a new scheme starts as a copy of the default scheme's rules; SPECIFIC_USER and GROUP parameters must reference existing users and groups; duplicates are de-duplicated; the default scheme and any scheme still assigned to a project cannot be deleted; and all changes are audited.

Restricted tickets and schemes

A ticket flagged restricted (issue security v1) is content-visible only to its reporter, its assignee, project ADMINs and global admins. Everyone else receives a 404 — existence is not confirmed — and the ticket is filtered out of lists, search and content-bearing reports. Inside the permission evaluator, a restricted ticket in scope blocks *every* scheme grant for principals who cannot see it, so no grant — ANYONE_LOGGED_IN, GROUP or otherwise — reaches a restricted ticket. Set the flag at creation (restricted: true; the creator is the reporter and keeps visibility) or toggle it later via a ticket edit, which requires EDIT_TICKET.

Documented v1 boundary: link lists (bare keys), watch state and aggregate-only counts (stats, CFD, throughput, workload) may still reflect a restricted ticket's *existence*; none expose its content. Restriction is orthogonal to permission schemes — a scheme grant can neither widen nor narrow restricted visibility.