Bounded Context: Approvals
Aggregate Root: Yes
Module: Ums.Domain.Approvals.AccessEnforcementPolicy
Status: Production
The AccessEnforcementPolicy aggregate root establishes broad access restriction rules at the tenant level. It determines which access privileges (such as a profile or role mapping) are blocked, degraded, or restricted when a user falls out of compliance (e.g. missing required valid credentials or expired critical documents).
AccessEnforcementPolicy acts as a separate aggregate root from DocumentType to separate credential definitions from security reaction rules.
ProfileId or a RoleId (or both). Creating a policy without specifying either target is prohibited (DomainErrors.Approvals.PolicyRequiresProfileOrRole).DomainErrors.Approvals.PolicyAlreadyInactive).TenantId to guarantee tenant partition safety.| Entity / VO | Type | Description |
|—|—|—|
| AccessEnforcementPolicyId | Value Object | Unique aggregate identifier |
| TenantId | Value Object | Owner tenant scope identifier |
| ProfileId | Value Object | Target Authorization Profile (Optional) |
| RoleId | Value Object | Target Authorization Role (Optional) |
| AccessEnforcementAction | Enum/VO | BLOCK_ACCESS · DEGRADE_ROLE · RESTRICT_API |
| AuditValueObject | Value Object | Multi-user audit history trail |
AccessEnforcementPolicy (Aggregate Root)
└── Props: AccessEnforcementPolicyProps
├── Id: AccessEnforcementPolicyId
├── TenantId: TenantId
├── ProfileId: ProfileId?
├── RoleId: RoleId?
├── EnforcementAction: AccessEnforcementAction
├── IsActive: bool
└── Audit: AuditValueObject
classDiagram
direction TB
class AccessEnforcementPolicy {
+AccessEnforcementPolicyProps Props
+Create(TenantId, ProfileId?, RoleId?, AccessEnforcementAction, ActorId) Result~AccessEnforcementPolicy~
+Deactivate(ActorId) Result
+UpdateAction(AccessEnforcementAction, ActorId) Result
}
class AccessEnforcementPolicyProps {
+IdValueObject Id
+TenantId TenantId
+ProfileId ProfileId
+RoleId RoleId
+AccessEnforcementAction EnforcementAction
+bool IsActive
+AuditValueObject Audit
}
class AccessEnforcementAction {
<<enumeration>>
BLOCK_ACCESS
DEGRADE_ROLE
RESTRICT_API
}
AccessEnforcementPolicy *-- AccessEnforcementPolicyProps
AccessEnforcementPolicyProps --> AccessEnforcementAction
sequenceDiagram
autonumber
actor Admin as Tenant Administrator
participant Portal as Web Client
participant App as Application Service
participant Policy as AccessEnforcementPolicy [Aggregate]
participant Repo as AccessEnforcementPolicyRepository
participant DB as SQL Server
Admin->>Portal: Configures new enforcement rule
Portal->>App: CreateAccessEnforcementPolicyCommand(ProfileId, Action)
App->>Policy: Create(TenantId, ProfileId, null, Action, AdminId)
note over Policy: Validate INV-AEP1<br/>(ProfileId or RoleId must be set)
Policy-->>App: AccessEnforcementPolicy [Success]
App->>Repo: SaveAsync(Policy)
Repo->>DB: INSERT INTO ACCESS_ENFORCEMENT_POLICY
DB-->>Repo: Acknowledge
Repo-->>App: Done
App-->>Portal: CreateAccessEnforcementPolicyResponse(Success)
erDiagram
ACCESS_ENFORCEMENT_POLICY {
uniqueidentifier PolicyId PK
uniqueidentifier TenantId FK "Configuration Context"
uniqueidentifier ProfileId FK "Authorization Context"
uniqueidentifier RoleId FK "Authorization Context"
nvarchar EnforcementAction
bit IsActive
nvarchar CreatedBy
datetime2 CreatedAt
nvarchar UpdatedBy
datetime2 UpdatedAt
}
TenantId. Tenancy filters must be applied to all reads and writes to enforce operational borders.flowchart TD
subgraph ConfigContext [Configuration Context]
T[Tenant]
end
subgraph AuthContext [Authorization Context]
P[Profile]
R[Role]
end
subgraph ApprovalsContext [Approvals Context]
AEP[AccessEnforcementPolicy]
end
AEP -->|scopes| T
AEP -.->|references| P
AEP -.->|references| R
public class AccessEnforcementPolicyConfiguration : IEntityTypeConfiguration<AccessEnforcementPolicy>
{
public void Configure(EntityTypeBuilder<AccessEnforcementPolicy> builder)
{
builder.ToTable("ACCESS_ENFORCEMENT_POLICY");
builder.HasKey(e => e.Id);
builder.OwnsOne(e => e.Props, props =>
{
props.Property(p => p.Id).HasColumnName("PolicyId");
props.Property(p => p.TenantId).HasColumnName("TenantId");
props.Property(p => p.ProfileId).HasColumnName("ProfileId");
props.Property(p => p.RoleId).HasColumnName("RoleId");
props.Property(p => p.EnforcementAction).HasConversion<string>().HasColumnName("EnforcementAction");
props.Property(p => p.IsActive).HasColumnName("IsActive");
props.OwnsOne(p => p.Audit);
});
}
}