Skip to content
Updated Sep 24, 2026 by Barča Dvořáková · Owner: analysisactiveentity Edit on GitHub

Entity: permissionSetGroupAssignment ​

Entity Type: Database table

Description: Grants one permissionSet to one group. This is the normal, expected granting mechanism for tenant-side access — a tenant user gets access by being a member of a group that holds one or more of these grants, rather than by a direct per-user grant (see permissionSetUserAssignment, reserved for the edge case).

Because a group belongs to exactly one tenant for its entire lifetime, this grant's tenant is always the group's own tenant — there is no independent tenantId to set or keep in sync. tenantId is carried on this row purely as a derived, denormalized value (set once at insert from group.tenantId by trigger, never independently writable) to make tenant-scoped listing fast; the actual permission check always resolves tenant scope via the live group, never by trusting this column alone.

A group may hold more than one Permission Set at once (additive, same as direct assignments). A row here can also be created automatically rather than by an administrator's own action: creating an unrestricted organization-visibility permissionSet (or a tenant later becoming active while one already exists) auto-provisions a group per tenant together with exactly one of these rows, linking that group to the set — see permissionSet ("Auto-provisioned across every tenant when created unrestricted"). Such a row is otherwise ordinary — createdBy records the actor who triggered the provisioning (the set's creator, or whoever activated the tenant), not a synthetic system actor, same as everywhere else in this model.

This entity is not read at evaluation time. It is the authoring source for a group's intended grants — an administrator adds or removes a Permission Set here — but the actual access a member holds is materialized as a row on permissionSetUserAssignment (sourceGroupId set to this group), kept in sync synchronously whenever this table, or the group's membership, changes: adding a Permission Set here propagates a new permissionSetUserAssignment row (with sourceGroupId set) to every current member; removing one deletes the corresponding rows. Evaluation reads only permissionSetUserAssignment; it never joins through this table or through groupMembership any more. A user's effective access from a group is still, in effect, the union across every group they belong to — it just now shows up as the union of their own materialized rows rather than a live join.

A user's tenant membership (see tenantUser) is derived from group membership and genuine direct grants together, and is never assigned directly.

Data Attributes Table ​

Attribute NameDescriptionData TypeDefault ValueRequired (= Nullable)UniqueFormatValidationsIndexExample
idPrimary key of the entity.UUIDGenerated in code (app layer)YesYesUUID v7-Primary Key018ed0b3-c298-7c7a-96d5-8b36f5a7f8d2
groupIdThe group this Permission Set is granted to.UUID-YesNoUUID v7Foreign Key → group; must existname: idx_permission_set_group_assignments_group, type: btree (active records)018fa51f-fda1-79f4-8461-2cb8f1cabc15
tenantIdDerived from group.tenantId at insert time (BEFORE INSERT trigger); immutable thereafter. Kept only for fast tenant-scoped queries — never trusted alone for an authorization decision.UUIDDerived from group.tenantId — set in triggerYesNoUUID v7Must equal the referenced group's tenantId; enforced by trigger, not independently writable.name: idx_permission_set_group_assignments_tenant, type: btree (active records)018fa51f-fda1-79f4-8461-2cb8f1cabc10
permissionSetIdThe Permission Set being granted.UUID-YesNoUUID v7Foreign Key → permissionSet; must exist, be active, and (restrictedToTenantId is null or equals this group's tenantId)name: idx_permission_set_group_assignments_set, type: btree (active records)018fa51f-fda1-79f4-8461-2cb8f1cabc11
createdAtTimestamp of when the record was created. Immutable after insert.Timestamp with time zonenow() — set in codeYesNoISO 8601Cannot be null; cannot be modified after creation.-2026-09-01T00:00:00Z
updatedAtTimestamp of the last update. Set on insert (equal to createdAt) and updated on every change.Timestamp with time zonenow() — set in codeYesNoISO 8601Cannot be null.-2026-09-01T00:00:00Z
deletedAtTimestamp of soft deletion (i.e. the grant was revoked). Null means the grant is active. Once set, immutable.Timestamp with time zone-NoNoISO 8601Immutable once set. Active records: WHERE deletedAt IS NULLIndexed (active records)-
createdByIdentifier of the actor who created the record.String-YesNotype:actorNon-empty.-user:018e...
updatedByIdentifier of the actor who last updated the record.String-YesNotype:actorNon-empty.-user:018e...

Uniqueness: (groupId, permissionSetId) is unique among active (non-deleted) records, enforced by unique index uidx_permission_set_group_assignments, type: btree — the same Permission Set cannot be granted twice to the same group. Tenant is implied by groupId, so it does not need to appear in this constraint.

Audited fields ​

Recorded (created/deleted: full set; updated: changed fields only): permissionSetId.

Filed under subject: groupId is not duplicated in this set — every entry sets the audit row's own subjectEntityType = "group" / subjectEntityId = groupId, so one group's full Permission Set grant history is a single indexed read.

Excluded:

  • id, createdAt, updatedAt, deletedAt, createdBy, updatedBy — redundant with the audit entry's own entityId/createdAt/createdBy, which already identify the record and the write.
  • groupId — filed as the audit row's subject reference instead (see above).
  • tenantId — derived from group.tenantId, redundant with the audit entry's own tenantId (RLS scope column) and with group's own trail.