ums

ApprovalRequest — Aggregate Architecture

Bounded Context: Approvals
Aggregate Root: ApprovalRequest
Module: Ums.Domain.Approvals.ApprovalRequest
Status: Production


1. Aggregate Overview

Purpose

The ApprovalRequest aggregate represents a concrete runtime execution of an approval process. When a user requests a sensitive action, such as profile assignment, profile promotion, or security configuration modification, UMS instantiates an ApprovalRequest linked to a specific ApprovalWorkflow to track status and audit details of that operational decision.

Business Responsibility

Aggregate Root

ApprovalRequest is the aggregate root. All state transitions (Approving, Rejecting) must flow through it to ensure constraints.

Invariants and Consistency Rules

  1. A request is born in the Pending status.
  2. The status can only transition from Pending to Approved or Rejected. Once a request has been finalized (Approved or Rejected), its status is permanently locked and cannot be edited.
  3. Must contain valid references to WorkflowId and TargetUserId.
  4. A user cannot approve their own promotion request (enforced at the application/domain command barrier to prevent collusion).

| Entity / VO | Type | Ownership | |—|—|—| | ApprovalRequestId | Value Object | Guid-based aggregate root identifier | | ApprovalStatus | Enum | Pending · Approved · Rejected | | SystemSuiteId | Value Object | Requested system scope | | BranchId | Value Object | Requested branch scope, when applicable | | RoleId | Value Object | Requested or granted role identifier | | AuditValueObject | Value Object | Tracks creation and modification metadata |

Domain Events

| Event | Trigger | |—|—| | ApprovalRequestCreatedEvent | A new approval request is registered and set to Pending | | ApprovalRequestApprovedEvent | The request is marked as Approved, triggering downstream activations | | ApprovalRequestRejectedEvent | The request is marked as Rejected. For EP-09 profile requests this is exposed to users as Denied |

Commands / Use Cases

| Command | Description | |—|—| | CreateApprovalRequestCommand | Instantiate a profile access request with requested system, branch, role, and justification | | ApproveRequestCommand | Approve a pending request with the authorized actor ID | | RejectRequestCommand | Reject a pending request. User-facing onboarding flows expose this final outcome as Denied |

Repository / Service Boundaries


2. Domain Model

Classes / Entities / Value Objects

ApprovalRequest (Aggregate Root)
└── Props: ApprovalRequestProps
    ├── Id: ApprovalRequestId
    ├── WorkflowId: ApprovalWorkflowId
    ├── TargetUserId: UserId
    ├── TargetProfileId?: ProfileId
    ├── RequestedSystemId: SystemSuiteId
    ├── RequestedBranchId?: BranchId
    ├── RequestedRoleId: RoleId
    ├── Justification?: string
    ├── Status: ApprovalStatus
    ├── GrantedRoleId?: RoleId
    ├── DecisionReason?: string
    └── Audit: AuditValueObject

3. Object Model Diagrams

classDiagram
    direction LR
    class ApprovalRequest {
        +Guid Id
        +Guid WorkflowId
        +Guid TargetUserId
        +Guid? TargetProfileId
        +Guid RequestedSystemId
        +Guid? RequestedBranchId
        +Guid RequestedRoleId
        +string? Justification
        +ApprovalStatus Status
        +Guid? GrantedRoleId
        +string? DecisionReason
        +Create()
        +Approve()
        +Reject()
    }
    class ApprovalStatus {
        <<enumeration>>
        Pending
        Approved
        Rejected
    }
    ApprovalRequest "1" *-- "1" ApprovalStatus

4. Sequence Diagrams

Complete Approval Request Lifecycle

sequenceDiagram
    participant U as Requester
    participant H as RequestHandler
    participant R as IApprovalRequestRepository
    participant A as ApprovalRequest (AR)
    participant E as EventPublisher
    participant Admin as Approver

    U->>H: CreateApprovalRequestCommand(workflowId, targetUserId, targetProfileId, requestedSystemId, requestedBranchId, requestedRoleId, justification)
    H->>A: ApprovalRequest.Create(workflowId, targetUserId, targetProfileId, requestedSystemId, requestedBranchId, requestedRoleId, justification, actorId)
    A->>A: Set Status = Pending
    A->>A: Raise ApprovalRequestCreatedEvent
    H->>R: Save(request)
    R-->>H: ok
    H-->>U: RequestId

    Note over U,A: Administrative Review
    Admin->>H: ApproveRequestCommand(requestId)
    H->>R: GetById(requestId)
    R-->>H: ApprovalRequest (AR)
    H->>A: Approve(adminActorId)
    A->>A: Transition Status -> Approved
    A->>A: Raise ApprovalRequestApprovedEvent
    H->>R: Save(request)
    R-->>H: ok
    H->>E: Publish ApprovalRequestApprovedEvent

Profile Request Onboarding Mapping

sequenceDiagram
    participant User as Lobby User
    participant H as ProfileRequestHandler
    participant R as IApprovalRequestRepository
    participant A as ApprovalRequest (AR)
    participant Admin as Tenant or Branch Approver
    participant Notify as Notification Service

    User->>H: Request profile access
    H->>A: ApprovalRequest.Create(workflowId, userId, targetProfileId, requestedSystemId, requestedBranchId, requestedRoleId, justification)
    A->>A: Set Status = Pending
    H->>R: Save(request)
    R-->>Admin: Pending profile request available in scoped inbox
    Admin->>H: Approve or deny final outcome
    H->>A: Approve() or Reject()
    A->>A: Store Approved or Rejected
    H->>Notify: Notify user with Approved or Denied business outcome

5. ER Model

erDiagram
    APPROVAL_WORKFLOW ||--o{ APPROVAL_REQUEST : "routes"
    USER_ACCOUNT ||--o{ APPROVAL_REQUEST : "targets"
    PROFILE ||--o{ APPROVAL_REQUEST : "elevates-to"

    APPROVAL_REQUEST {
        uniqueidentifier Id PK
        uniqueidentifier WorkflowId FK
        uniqueidentifier TargetUserId FK
        uniqueidentifier TargetProfileId FK "Nullable"
        uniqueidentifier RequestedSystemId FK
        uniqueidentifier RequestedBranchId FK "Nullable"
        uniqueidentifier RequestedRoleId FK
        nvarchar Justification "Nullable"
        int StatusId "1=Pending, 2=Approved, 3=Rejected"
        uniqueidentifier GrantedRoleId FK "Nullable"
        nvarchar DecisionReason "Nullable"
        nvarchar CreatedBy
        datetime2 CreatedAtUtc
        nvarchar UpdatedBy "Nullable"
        datetime2 UpdatedAtUtc "Nullable"
        nvarchar AuditTimeSpan
        rowversion RowVersion
    }

Tenant Isolation Rules

EP-09 Required Extension

The implemented ApprovalRequest record now stores the requested system, branch, role, justification, granted role, and decision reason. Final notification delivery is handled by the notification pipeline, so the remaining FS-24 follow-up is only needed if a persisted notification-result field becomes a design requirement.


6. Bounded Context Integration


7. Application Layer


8. Infrastructure/Persistence


9. Security & Compliance


10. Technical Decisions


Back to Approvals Index