Bounded Context: Authorization
Aggregate Root: Profile
Module: Ums.Domain.Authorization.Profile
Status: Production
The Profile aggregate represents an effective authorization assignment for a user inside a tenant. It binds a UserId to a RoleId and optionally to a BranchId, then materializes effective permissions from published PermissionTemplate definitions. It is the parent container for ProfilePermission owned entities and the operational source used by downstream access checks.
ProfilePermission entries.Active / Inactive) for the whole profile.Profile is the aggregate root. Template linkage, permission overrides, permission activation/deactivation, and aggregate status changes must go through Profile.
TenantId, UserId, and RoleId are mandatory for every Profile.Scope is derived from BranchId: no branch means OrgWide; a branch means BranchScoped.Profile can only link PermissionTemplate instances from the same tenant.Profile can only link templates that are already Published.Profile cannot link the same template twice.Profile is active.ProfilePermission identity is materialized per template item and keeps source lineage through TemplateId.| Entity / VO | Type | Ownership | Description |
|—|—|—|—|
| ProfilePermission | Entity | Owned | Effective permission materialized from a template item |
| ProfileScope | Enumeration | - | OrgWide or BranchScoped |
| TenantId | Value Object | - | Tenant ownership boundary |
| UserId | Value Object | - | User receiving the effective profile |
| RoleId | Value Object | - | Role source for template selection |
| BranchId | Value Object | - | Optional branch scoping |
| TemplateId | Value Object | - | Source template lineage for materialized permissions |
| Event | Trigger |
|—|—|
| ProfileCreatedEvent | New profile created |
| TemplateLinkedToProfileEvent | Published template linked and materialized into permissions |
| PermissionOverriddenEvent | Allow / deny / neutral / activate / deactivate applied to a permission |
| ProfileDeactivatedEvent | Profile deactivated |
| ProfileActivatedEvent | Profile reactivated |
Profile (Aggregate Root)
├── Props: ProfileProps
│ ├── Id: IdValueObject
│ ├── TenantId: TenantId
│ ├── UserId: UserId
│ ├── RoleId: RoleId
│ ├── BranchId?: BranchId
│ ├── Scope: ProfileScope
│ ├── IsActive: bool
│ └── Audit: AuditValueObject
└── Children
└── IReadOnlyCollection<ProfilePermission>
└── Props: ProfilePermissionProps
├── Id: IdValueObject
├── ProfileId: ProfileId
├── TemplateId: TemplateId
├── TargetType: ExclusiveArcTarget
├── TargetId: IdValueObject
├── ActionId: ActionId
├── IsAllowed: bool
├── IsDenied: bool
├── IsActive: bool
├── IsOverride: bool
└── Audit: AuditValueObject
classDiagram
direction TB
class Profile {
+Guid Id
+Guid TenantId
+Guid UserId
+Guid RoleId
+Guid? BranchId
+ProfileScope Scope
+bool IsActive
+List~ProfilePermission~ Permissions
+Create(tenantId, userId, roleId, branchId, actor)
+AssignTemplate(template, actor)
+OverridePermissionAllow(permissionId, actor)
+OverridePermissionDeny(permissionId, actor)
+OverridePermissionNeutral(permissionId, actor)
+ActivatePermission(permissionId, actor)
+DeactivatePermission(permissionId, actor)
+Activate(actor)
+Deactivate(actor)
}
class ProfilePermission {
+Guid Id
+Guid ProfileId
+Guid TemplateId
+ExclusiveArcTarget TargetType
+Guid TargetId
+Guid ActionId
+bool IsAllowed
+bool IsDenied
+bool IsActive
+bool IsOverride
}
Profile "1" *-- "0..*" ProfilePermission
sequenceDiagram
participant C as Client
participant H as Handler
participant R as IProfileRepository
participant P as Profile (AR)
participant T as PermissionTemplate
C->>H: CreateProfileCommand(tenantId, userId, roleId, branchId)
H->>P: Profile.Create(tenantId, userId, roleId, branchId, actor)
P->>P: Derive Scope from BranchId
P->>P: Raise ProfileCreatedEvent
H->>R: Add(profile)
R-->>H: ok
H->>T: Load published template for tenant/role
H->>P: profile.AssignTemplate(template, actor)
P->>P: Validate tenant and published status
P->>P: Materialize ProfilePermission items
P->>P: Raise TemplateLinkedToProfileEvent
H->>R: Update(profile)
R-->>H: ok
H-->>C: ProfileId
sequenceDiagram
participant C as Client
participant H as Handler
participant R as IProfileRepository
participant P as Profile (AR)
C->>H: OverridePermissionAllow(profileId, permissionId)
H->>R: GetById(profileId)
R-->>H: Profile
H->>P: OverridePermissionAllow(permissionId, actor)
P->>P: Validate active profile
P->>P: Mark permission as override
P->>P: Raise PermissionOverriddenEvent
H->>R: Update(profile)
R-->>H: ok
H-->>C: Success
erDiagram
PROFILE ||--o{ PROFILE_PERMISSION : "contains"
TENANT ||--o{ PROFILE : "owns"
USER_ACCOUNT ||--o{ PROFILE : "receives"
ROLE ||--o{ PROFILE : "sources"
BRANCH ||--o{ PROFILE : "scopes"
PERMISSION_TEMPLATE ||--o{ PROFILE_PERMISSION : "materializes"
ACTION ||--o{ PROFILE_PERMISSION : "enforces"
PROFILE {
uniqueidentifier Id PK
uniqueidentifier TenantId FK
uniqueidentifier UserId FK
uniqueidentifier RoleId FK
uniqueidentifier BranchId FK "Nullable"
int ScopeId "1=OrgWide, 2=BranchScoped"
bit IsActive
nvarchar CreatedBy
datetime2 CreatedAtUtc
nvarchar UpdatedBy "Nullable"
datetime2 UpdatedAtUtc "Nullable"
nvarchar AuditTimeSpan
}
PROFILE_PERMISSION {
uniqueidentifier Id PK
uniqueidentifier ProfileId FK
uniqueidentifier TemplateId FK
int TargetTypeId
uniqueidentifier TargetId
uniqueidentifier ActionId FK
bit IsAllowed
bit IsDenied
bit IsActive
bit IsOverride
nvarchar CreatedBy
datetime2 CreatedAtUtc
nvarchar UpdatedBy "Nullable"
datetime2 UpdatedAtUtc "Nullable"
nvarchar AuditTimeSpan
}
Profile is always tenant-owned in the current implementation; TenantId is required.ScopeId = OrgWide, not through a null TenantId.PROFILE_PERMISSION inherits isolation scope from its parent Profile.TenantId, UserId, and BranchId from the Identity Bounded Context.RoleId and published PermissionTemplate definitions from the Authorization Context.ActionId and target topology from SystemSuite.CreateProfileCommand -> Inputs: TenantId, UserId, RoleId, BranchId? -> Returns: GuidProfile transaction boundary.[ums_authorization].[Profiles][ums_authorization].[ProfilePermissions]Profile: TenantId, UserId, (TenantId, UserId, RoleId, BranchId)ProfilePermission: ProfileId, (ProfileId, TemplateId, ActionId, TargetId)Profile is an effective authorization assignment, not a named catalog role.Scope is persisted as an enumeration identifier (ScopeId) and derived from BranchId at creation time.TemplateId, allowing re-evaluation, auditing, and future rebuild workflows.IsOverride and state toggles in ProfilePermission, instead of mutating PermissionTemplate.