You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Bounded Context: Authorization Aggregate Root:PermissionTemplate Module:Ums.Domain.Authorization.Template Status: Production
1. Aggregate Overview
Purpose
PermissionTemplate defines a reusable, versioned package of access rules for a specific Role within a SystemSuite. It acts as the authoritative blueprint for seeding Profile permissions when a new tenant is onboarded or a new role assignment is activated. Each item in the template targets a level of the SystemSuite hierarchy (suite → module → submodule → option) using an Exclusive Arc pattern, with a tri-state effect (IsAllowed, IsDenied, or neutral).
Business Responsibility
Create and version access-rule packages tied to a (TenantId, RoleId, SystemSuiteId) combination.
Manage the template lifecycle: Draft → Published → Deprecated.
Own a collection of PermissionTemplateItem entries, each targeting one hierarchy level of a SystemSuite with a specific permission effect.
Enforce that items can only be added, mutated, or removed while the template is in Draft status.
Allow deletion only from Draft or Deprecated states, and block deletion while active profile dependencies still exist.
Provide the canonical rule set consumed by downstream Profile provisioning.
Aggregate Root
PermissionTemplate is the sole aggregate root. PermissionTemplateItem is an owned child entity managed exclusively through the parent aggregate — never accessed or mutated directly.
State Machine
stateDiagram-v2
[*] --> Draft : Create
Draft --> Published : Publish\n(requires ≥1 item)
Published --> Deprecated : Deprecate
Deprecated --> [*]
note right of Draft : Items can be added,\nremoved, and mutated only\nwhen status = Draft
note right of Published : Read-only — used for\nProfile seeding
note right of Deprecated : Terminal — superseded\nby a newer template
Loading
Invariants and Consistency Rules
ID
Rule
Enforced by
INV-TPL1
(TenantId, RoleId, SystemSuiteId) combination must be unique per active template
Repository uniqueness check
INV-TPL2
Items can only be added, removed, or mutated when Status = Draft
TemplateNotDraft guard in every mutating method
INV-TPL3
A Published template cannot be re-drafted; a new template must be created
TemplateNotPublished guard on Deprecate
INV-TPL4
(TargetType, TargetId, ActionId) must be unique within a template — no duplicate item mappings
TemplateItemTargetAlreadyExists guard in AddItem
INV-TPL5
An item cannot have IsAllowed = true AND IsDenied = true simultaneously
PermissionEffectValidator on every item
INV-TPL6
TargetId must never be null — Exclusive Arc requires a concrete target reference
ExclusiveArcValidator on every item
Related Entities / Value Objects
Entity / VO
Type
Ownership
Notes
PermissionTemplateItem
Entity
Owned
One entry per (TargetType, TargetId, ActionId) triplet
templateId, tenantId, roleId, systemSuiteId, version
PermissionTemplatePublishedEvent
Template published
templateId, version
PermissionTemplateMutatedEvent
Item added, removed, or effect changed
templateId, version
PermissionTemplateDeprecatedEvent
Template deprecated
templateId, version
PermissionTemplateDeletedEvent
Template deleted
templateId, version
Note: A single PermissionTemplateMutatedEvent covers all item-level changes (add, remove, allow/deny/neutral toggle, activate/deactivate). Downstream consumers react to any mutation on the template version, not to individual item operations.
Commands / Use Cases
Command
Description
Pre-condition
CreatePermissionTemplateCommand
Draft a new template for (tenantId, roleId, systemSuiteId)
No active template for that triplet
PublishPermissionTemplateCommand
Transition Draft → Published
Status = Draft
AddTemplateItemCommand
Add an item (targetType, targetId, actionId, isAllowed, isDenied)
Status = Draft; no duplicate triplet
SetItemAllowCommand
Set IsAllowed=true, IsDenied=false
Status = Draft
SetItemDenyCommand
Set IsAllowed=false, IsDenied=true
Status = Draft
SetItemNeutralCommand
Set IsAllowed=false, IsDenied=false (inherit from parent scope)
Status = Draft
ActivateItemCommand
Set IsActive=true
Status = Draft
DeactivateItemCommand
Set IsActive=false
Status = Draft
RemoveTemplateItemCommand
Remove an item from the template
Status = Draft
DeprecatePermissionTemplateCommand
Transition Published → Deprecated
Status = Published
DeletePermissionTemplateCommand
Remove a template from storage
Status = Draft or Deprecated; no active profile dependencies
Repository / Service Boundaries
IPermissionTemplateRepository — persists PermissionTemplate including owned items.
ITemplateUniquenessChecker — validates INV-TPL1 before creation (no duplicate active template per triplet).
classDiagram
direction TB
class PermissionTemplate {
<<AggregateRoot>>
+Guid Id
+Guid TenantId
+Guid RoleId
+Guid SystemSuiteId
+TemplateVersion Version
+TemplateStatus Status
+AuditValueObject Audit
+IReadOnlyCollection~PermissionTemplateItem~ Items
+Create(tenantId, roleId, systemSuiteId, createdBy) Result
+Publish(updatedBy) Result
+Deprecate(updatedBy) Result
+AddItem(targetType, targetId, actionId, isAllowed, isDenied, createdBy) Result
+SetItemAllow(itemId, updatedBy) Result
+SetItemDeny(itemId, updatedBy) Result
+SetItemNeutral(itemId, updatedBy) Result
+ActivateItem(itemId, updatedBy) Result
+DeactivateItem(itemId, updatedBy) Result
+RemoveItem(itemId, updatedBy) Result
+Delete(deletedBy, activeProfileCount) Result
}
class PermissionTemplateItem {
<<OwnedEntity>>
+Guid Id
+Guid TemplateId
+ExclusiveArcTarget TargetType
+Guid TargetId
+Guid ActionId
+bool IsAllowed
+bool IsDenied
+bool IsActive
+AuditValueObject Audit
+SetAllow(updatedBy) Result
+SetDeny(updatedBy) Result
+SetNeutral(updatedBy) Result
+Activate(updatedBy) Result
+Deactivate(updatedBy) Result
}
class TemplateStatus {
<<Enumeration>>
Draft = 1
Published = 2
Deprecated = 3
}
class ExclusiveArcTarget {
<<Enumeration>>
SystemSuite = 1
Module = 2
Submodule = 3
Option = 4
}
PermissionTemplate "1" *-- "0..*" PermissionTemplateItem : owns
PermissionTemplate --> TemplateStatus
PermissionTemplateItem --> ExclusiveArcTarget
Loading
4. Sequence Diagrams
Create Template & Publish Flow
sequenceDiagram
participant A as Admin
participant H as CreatePermissionTemplateHandler
participant U as ITemplateUniquenessChecker
participant T as PermissionTemplate (AR)
participant R as IPermissionTemplateRepository
A->>H: CreatePermissionTemplateCommand(tenantId, roleId, systemSuiteId)
H->>U: IsUnique(tenantId, roleId, systemSuiteId)
U-->>H: true
H->>T: PermissionTemplate.Create(...)
T->>T: Status = Draft · Version = "0.1.0"
T->>T: Raise PermissionTemplateCreatedEvent
H->>R: Add(template)
H-->>A: templateId
Note over A,R: Admin adds items...
A->>H: PublishPermissionTemplateCommand(templateId)
H->>R: GetById(templateId)
R-->>H: PermissionTemplate (Draft)
H->>T: template.Publish(actorId)
T->>T: Guard: Status must be Draft
T->>T: Status = Published
T->>T: Raise PermissionTemplatePublishedEvent
H->>R: Update(template)
H-->>A: void
Loading
Add Item Flow (Exclusive Arc)
sequenceDiagram
participant A as Admin
participant H as AddTemplateItemHandler
participant T as PermissionTemplate (AR)
participant R as IPermissionTemplateRepository
A->>H: AddTemplateItemCommand(templateId, targetType, targetId, actionId, isAllowed, isDenied)
H->>R: GetById(templateId)
R-->>H: PermissionTemplate (Draft)
H->>T: template.AddItem(targetType, targetId, actionId, isAllowed, isDenied, actorId)
T->>T: Guard: Status = Draft
T->>T: Guard: no duplicate (TargetType, TargetId, ActionId)
T->>T: ExclusiveArcValidator — TargetId not null
T->>T: PermissionEffectValidator — not (IsAllowed ∧ IsDenied)
T->>T: Raise PermissionTemplateMutatedEvent
H->>R: Update(template)
H-->>A: itemId
Loading
Permission Effect Override Flow
sequenceDiagram
participant A as Admin
participant H as SetItemDenyHandler
participant T as PermissionTemplate (AR)
A->>H: SetItemDenyCommand(templateId, itemId)
H->>R: GetById(templateId)
R-->>H: PermissionTemplate (Draft)
H->>T: template.SetItemDeny(itemId, actorId)
T->>T: Guard: Status = Draft
T->>T: item.IsDenied = true · item.IsAllowed = false
T->>T: Raise PermissionTemplateMutatedEvent
H->>R: Update(template)
H-->>A: void
Loading
5. Entity / Relationship Model
Exclusive Arc:PERMISSION_TEMPLATE_ITEM.TargetId is a polymorphic FK — it can reference a SystemSuite, Module, Submodule, Option, Aggregate, or Entity depending on TargetTypeId. This is the Exclusive Arc pattern: only one of the possible references is valid per row, identified by TargetTypeId. No SQL foreign-key constraint spans all targets; referential integrity is enforced at the application layer.
erDiagram
PERMISSION_TEMPLATE ||--o{ PERMISSION_TEMPLATE_ITEM : "contains"
PERMISSION_TEMPLATE }o--|| TENANT : "scoped_to"
PERMISSION_TEMPLATE }o--|| ROLE : "packages_for"
PERMISSION_TEMPLATE }o--|| SYSTEM_SUITE : "covers"
PERMISSION_TEMPLATE_ITEM }o--|| ACTION : "targets_action"
PERMISSION_TEMPLATE {
uniqueidentifier Id PK
uniqueidentifier TenantId FK "RLS scope"
uniqueidentifier RoleId FK "role being templated"
uniqueidentifier SystemSuiteId FK "suite context"
nvarchar Version "semver — 0.1.0"
int StatusId "1=Draft 2=Published 3=Deprecated"
nvarchar CreatedBy
datetime2 CreatedAtUtc
nvarchar UpdatedBy "Nullable"
datetime2 UpdatedAtUtc "Nullable"
nvarchar AuditTimeSpan
}
PERMISSION_TEMPLATE_ITEM {
uniqueidentifier Id PK
uniqueidentifier TemplateId FK
int TargetTypeId "1=SystemSuite 2=Module 3=Submodule 4=Option 5=Aggregate 6=Entity"
uniqueidentifier TargetId "Exclusive Arc — points to entity of TargetTypeId"
uniqueidentifier ActionId FK "→ Action catalog"
bit IsAllowed "true = explicit Allow"
bit IsDenied "true = explicit Deny — CHECK NOT (IsAllowed AND IsDenied)"
bit IsActive "false = skip during Profile seeding"
nvarchar CreatedBy
datetime2 CreatedAtUtc
nvarchar UpdatedBy "Nullable"
datetime2 UpdatedAtUtc "Nullable"
nvarchar AuditTimeSpan
}
ACTION {
uniqueidentifier Id PK
nvarchar Code "unique action code"
nvarchar Name
}
PermissionTemplate and all its PermissionTemplateItem children are saved in a single SaveChanges() call. The PermissionTemplatePublishedEvent is dispatched via Transactional Outbox to trigger downstream Profile seeding.