Bilingual navigation: Español
Owner: Evolith Architecture Board
Status: Active reference
Parent: UMS Applied Reference Hub
The other documents in this section establish the governance relationship between Evolith and UMS. This document does something different: it explains what UMS actually is — its vision, technical complexity, bounded contexts, key patterns, and how to navigate into it from here. It is the bridge that keeps you inside Evolith while giving you a complete architectural picture before going deeper.
Enterprise software fails at identity and access management in predictable ways:
- Permissions scattered across every application instead of being centrally governed
- No audit trail that can answer "who had access to what, when, and why"
- Tenant isolation built as an afterthought, impossible to verify under load
- Authorization logic duplicated in every service — inconsistent, untestable, unmaintainable
- Role management done manually, creating security debt at scale (IGA problem)
UMS is the answer to all five simultaneously. It is a User Management System designed to govern identity, fine-grained authorization, multi-tenant isolation, immutable auditing, access approvals, compliance enforcement, and Identity Governance & Administration (IGA) — in a single, architecturally disciplined, progressively built product.
flowchart LR
classDef problem fill:#7f1d1d,stroke:#ef4444,color:#fff,font-size:12px
classDef solution fill:#14532d,stroke:#22c55e,color:#fff,font-size:12px
P1[" Permissions scattered\nacross every app"]:::problem
P2[" No audit trail\n(who · what · when · why)"]:::problem
P3[" Tenant isolation\nas an afterthought"]:::problem
P4[" Authorization logic\nduplicated in every service"]:::problem
P5[" Manual role management\n→ IGA security debt"]:::problem
S1[" EP-02 Authorization\nCentral permission graph\n+ contextual templates"]:::solution
S2[" EP-04 Audit\nImmutable append-only log\n10-column standard schema"]:::solution
S3[" EP-01 + EP-03\nDual-layer RLS\n(id, root_tenant_id) on every table"]:::solution
S4[" EP-02 Authorization\nXACML PEP/PDP/PAP/PIP\ncompiled at resolution time"]:::solution
S5[" EP-08 IGA\nRole Maturity Model (5 levels)\nPromotion lifecycle engine"]:::solution
P1 -->|"solved by"| S1
P2 -->|"solved by"| S2
P3 -->|"solved by"| S3
P4 -->|"solved by"| S4
P5 -->|"solved by"| S5
UMS earns this role because it:
- Exercises the hardest architectural problems — multi-tenancy, hierarchical authorization graphs, distributed sagas, and IGA are not toy problems
- Demonstrates every core Evolith ADR in real, running code
- Lives in Evolith Phase 1 — a modular monolith with schema-per-context and strict Hexagonal Architecture boundaries, ready to extract to Phase 2 when the criteria are met
- Is fully traceable — every functional story traces to an ADR, which traces to a technical enabler, which traces to code
flowchart TB
classDef entity fill:#1e3a5f,stroke:#3b82f6,color:#fff,font-weight:bold
classDef permgraph fill:#7f1d1d,stroke:#ef4444,color:#fff,font-weight:bold
classDef cross fill:#14532d,stroke:#22c55e,color:#fff,font-size:12px
classDef decision fill:#4a3800,stroke:#f59e0b,color:#fff
USERS[" Users"]:::entity
ORGS[" Organizations\n(multi-tenant)"]:::entity
ROLES[" Roles"]:::entity
TEMPLATES[" Authorization Templates"]:::entity
GRAPH["️ Permission Graph\n(compiled DAG — TE-02)"]:::permgraph
REQUEST[" Access Decision\n(granted / denied)"]:::decision
USERS -->|"belong to"| ORGS
USERS -->|"assigned to"| ROLES
ROLES -->|"grant"| TEMPLATES
ORGS -->|"govern"| TEMPLATES
ROLES & TEMPLATES -->|"compiled into"| GRAPH
GRAPH -->|"evaluated at"| REQUEST
AUD[" Immutable Audit\n(EP-04 — every action)"]:::cross
APP[" Approval Workflow\n(EP-06 — sensitive ops)"]:::cross
COMP[" Compliance Check\n(EP-07 — doc expiry)"]:::cross
IGA_N[" IGA Lifecycle\n(EP-08 — role maturity)"]:::cross
REQUEST -.->|"logged"| AUD
REQUEST -.->|"gated"| APP
USERS -.->|"tracked"| COMP
ROLES -.->|"governed"| IGA_N
UMS is decomposed into 8 strategic bounded contexts. Each is an independently deployable candidate and owns its schema, domain model, and contracts.
| # | Context | Core Responsibility | Phase | Key ADRs |
|---|---|---|---|---|
| EP-01 | Identity | User lifecycle, authentication, password policies, hosted login redirect, MFA/Passwordless | MVP | ADR-0020, ADR-0026 |
| EP-02 | Authorization | RBAC/ABAC templates, permission graph compilation, contextual projections, Visual Graph Resolver | MVP | ADR-0012, ADR-0021, ADR-0022 |
| EP-03 | Configuration | Hierarchical config (ENV > SYSTEM > TENANT), cached resolution with TTL, CQRS projection | MVP | ADR-0024, ADR-0034 |
| EP-04 | Audit | Immutable event log, 10-column audit schema on every table, append-only writes | MVP | ADR-0016 |
| EP-05 | Console / Admin | Administrative UI, tenant management, system topology registration | MVP | ADR-0008, ADR-0030 |
| EP-06 | Approvals | Adaptive MFA risk scoring (6 factors), B2B external access, delegated administration (5 scope types, 8 states) | Post-MVP | ADR-0035, ADR-0015 |
| EP-07 | Compliance | Document upload, expiration notifications (5 channels), access enforcement (3 modes), background engines | Post-MVP | ADR-0033, ADR-0036 |
| EP-08 | IGA | Role promotion lifecycle (6 stories), Role Maturity Model (5 levels), Promotion Impact Analysis engine, state machine (8 states) | Post-MVP | ADR-0035, ADR-0039 |
flowchart TB
classDef mvp fill:#1e3a5f,stroke:#3b82f6,color:#fff,font-weight:bold
classDef postmvp fill:#14532d,stroke:#22c55e,color:#fff
classDef shared fill:#4a3800,stroke:#f59e0b,color:#fff
IDENTITY["EP-01\nIdentity\n(Authentication · MFA)"]:::mvp
AUTHZ["EP-02\nAuthorization\n(Graph · RBAC/ABAC)"]:::mvp
CONFIG["EP-03\nConfiguration\n(Hierarchical · TTL Cache)"]:::mvp
AUDIT["EP-04\nAudit\n(Immutable · Append-only)"]:::mvp
CONSOLE["EP-05\nConsole / Admin\n(Tenant Mgmt · UI)"]:::mvp
APPROVALS["EP-06\nApprovals\n(MFA Risk · Delegated Admin)"]:::postmvp
COMPLIANCE["EP-07\nCompliance\n(Docs · Enforcement)"]:::postmvp
IGA["EP-08\nIGA\n(Role Promotion · Maturity)"]:::postmvp
OUTBOX["Transactional Outbox\n(TE-04 — ADR-0033)"]:::shared
SAGA["Distributed Saga\n(TE-05 — ADR-0035)"]:::shared
IDENTITY -->|"user authenticated"| AUTHZ
IDENTITY -->|"login event"| AUDIT
AUTHZ -->|"permission resolved"| CONSOLE
AUTHZ -->|"template assigned"| OUTBOX
CONFIG -->|"tenant config resolved"| IDENTITY & AUTHZ
APPROVALS -->|"approval requested"| SAGA
SAGA --> AUTHZ & IDENTITY
COMPLIANCE -->|"access blocked"| AUTHZ
IGA -->|"role promoted"| SAGA
OUTBOX --> AUDIT
CONSOLE -->|"admin action"| AUDIT
Most tutorials show "User + Role = Permission." UMS solves problems that make that model collapse at enterprise scale:
A user's effective permissions are not stored — they are compiled at resolution time from a directed acyclic graph of roles, templates, organizational hierarchy, and contextual overrides. This compilation is the heart of EP-02 and requires the high-performance graph compiler described in ADR-0021.
flowchart LR
classDef user fill:#1e3a5f,stroke:#3b82f6,color:#fff
classDef role fill:#4a3800,stroke:#f59e0b,color:#fff
classDef template fill:#14532d,stroke:#22c55e,color:#fff
classDef org fill:#4a1a6b,stroke:#9c27b0,color:#fff
classDef result fill:#7f1d1d,stroke:#ef4444,color:#fff,font-weight:bold
U["User: Alice"]:::user
R1["Role: Manager"]:::role
R2["Role: Auditor"]:::role
T1["Template: CanViewReports"]:::template
T2["Template: CanApproveUsers"]:::template
T3["Template: ReadOnlyAudit"]:::template
ORG["Org: ACME Corp\n(tenant boundary)"]:::org
OVERRIDE["Context: Department=Finance\n(override: CanExportData=true)"]:::org
U --> R1 & R2
R1 --> T1 & T2
R2 --> T3
ORG --> OVERRIDE
COMPILER[" Graph Compiler\n(TE-02 — ADR-0021)\ncompiles DAG at resolution time"]:::result
R1 & R2 & OVERRIDE --> COMPILER
COMPILER --> EFFECTIVE[" Effective Permissions\n{CanViewReports, CanApproveUsers,\nReadOnlyAudit, CanExportData}"]:::template
Every table carries a composite primary key (id, root_tenant_id) and is protected by two independent security layers: an EF Core global query filter (always active) and a SQL Server RLS predicate (failsafe). A bug in one layer cannot expose cross-tenant data. This is the two-layer model from ADR-0010.
flowchart TD
classDef req fill:#1e3a5f,stroke:#3b82f6,color:#fff
classDef layer fill:#4a3800,stroke:#f59e0b,color:#fff,font-weight:bold
classDef pass fill:#14532d,stroke:#22c55e,color:#fff
classDef block fill:#7f1d1d,stroke:#ef4444,color:#fff
REQ[" Incoming Request\nUser: Alice (tenant_id: ACME)"]:::req
L1["LAYER 1 — EF Core Global Query Filter\nAutomatically appends WHERE root_tenant_id = @tid\nto every query via DbContext interceptor\n(ADR-0010 PRIMARY)"]:::layer
L2["LAYER 2 — SQL Server RLS Predicate\nDATABASE-LEVEL filter: fn_SecurityPredicate()\nactivated via SESSION_CONTEXT\n(ADR-0010 FAILSAFE)"]:::layer
DB["️ SQL Server 2022\nRow-Level Security"]:::pass
BUG["️ If a bug bypasses Layer 1\n(e.g. raw query escapes EF Core)"]:::block
SAFE[" Layer 2 blocks at DB level\nData never leaves tenant boundary"]:::pass
REQ --> L1
L1 -->|"query filtered"| L2
L2 -->|"both pass"| DB
L1 -.->|"bug scenario"| BUG
BUG --> L2
L2 --> SAFE
Approvals (EP-06) and IGA (EP-08) require multi-step workflows that span multiple bounded contexts with compensating transactions. A role promotion, for example, triggers authorization graph recompilation, audit logging, compliance checks, and notification dispatch — all of which must roll back atomically if any step fails. This is governed by ADR-0035 (Distributed Sagas via Dapr).
Roles are not static. They have a lifecycle: proposed, under review, validated, active, deprecated. The Role Maturity Model (5 levels) in EP-08 drives promotion decisions using a Promotion Impact Analysis engine that evaluates blast radius before granting elevation. This is non-trivial authorization governance.
Immutability is not optional. Every write to UMS generates an audit record that cannot be updated or deleted, carries the full (who, what, when, from_where, tenant, correlation_id) envelope, and is queryable by compliance officers independently of the operational data. ADR-0016 governs this.
flowchart TB
classDef client fill:#4a1a6b,stroke:#9c27b0,color:#fff
classDef api fill:#1e3a5f,stroke:#3b82f6,color:#fff
classDef app fill:#14532d,stroke:#22c55e,color:#fff
classDef domain fill:#7f1d1d,stroke:#ef4444,color:#fff,font-weight:bold
classDef infra fill:#4a3800,stroke:#f59e0b,color:#fff
classDef data fill:#374151,stroke:#9ca3af,color:#fff
subgraph PRESENTATION["️ Presentation Layer"]
direction LR
WEB_UI["Razor Pages / React\nAdmin Console UI"]:::client
API_LAYER["REST Controllers\n+ GraphQL Resolvers\n(ADR-0032)"]:::api
end
subgraph APPLICATION["️ Application Layer (Use Cases)"]
direction LR
UC["Use Cases / Handlers\nNo framework imports\n(ADR-0002 Hexagonal)"]:::app
PORTS["Port Interfaces\nIUserRepository · IEventBus\nICache · IIdentityProvider"]:::app
end
subgraph DOMAIN["️ Domain Layer (Pure)"]
direction LR
AGG["Aggregates · Entities · Value Objects\nZero infrastructure imports\n(ADR-0002 hard constraint)"]:::domain
EVT["Domain Events\nUserCreated · RolePromoted\nTemplateAssigned"]:::domain
end
subgraph INFRA[" Infrastructure Layer (Adapters)"]
direction LR
EF["EF Core 8 DbContext\n+ Dapper read projections\n(ADR-0057)"]:::infra
RLS_A["SESSION_CONTEXT + RLS\nTenant isolation\n(ADR-0010)"]:::infra
BUS["Injectable Event Bus\nIn-process → RabbitMQ\n(ADR-0015)"]:::infra
OUTBOX_A["Transactional Outbox\n(ADR-0033)"]:::infra
CACHE["Redis Cache\n4-tier strategy\n(ADR-0014)"]:::infra
IDP["IdP Adapter\nKeycloak / AzureAD\n(ADR-0020)"]:::infra
end
subgraph DATA_LAYER["️ Data Layer"]
direction LR
SQL["SQL Server 2022\n8 schemas · RLS predicates\nClosure table · Temporal tables"]:::data
REDIS_DB["Redis Cluster"]:::data
MQ["RabbitMQ\nFIFO · DLQ\n(ADR-0036)"]:::data
end
PRESENTATION --> APPLICATION
APPLICATION --> DOMAIN
APPLICATION --> INFRA
INFRA --> DATA_LAYER
| Layer | Technology | Version | Governing ADR |
|---|---|---|---|
| Backend | .NET / C# | 8 / 12+ | Engineering Manifesto |
| ORM | Entity Framework Core | 8 | ADR-0057 |
| Database | SQL Server | 2022 | ADR-0051 |
| Multi-tenancy RLS | EF Core filter + SQL Server RLS | — | ADR-0010, ADR-0044 |
| Authorization | XACML-inspired (PEP/PDP/PAP/PIP) | — | ADR-0039 |
| Event bus | Injectable (in-process → RabbitMQ) | — | ADR-0015 |
| Outbox | Transactional Outbox pattern | — | ADR-0033 |
| Sagas | Distributed Sagas via Dapr | — | ADR-0035 |
| CQRS | EF Core write / Dapper read | — | ADR-0034 |
| Configuration | Hierarchical resolution + TTL cache | — | ADR-0024 |
| Identity | OIDC / JWT (IdP-abstracted) | — | ADR-0020 |
| Observability | OpenTelemetry + Loki + Grafana | — | ADR-0007 |
| Testing | xUnit + NSubstitute + Testcontainers | — | ADR-0018, ADR-0052 |
| CI/CD | GitHub Actions | — | ADR-0005 |
Technical enablers are the cross-cutting infrastructure investments that make the functional stories possible. UMS has 6:
| Enabler | What it builds | Satisfies |
|---|---|---|
| TE-01 — JWT / OIDC Flow | Token validation, IdP abstraction layer, refresh rotation | FS-01, FS-08, FS-09 |
| TE-02 — Permission Graph Compiler | High-performance DAG compilation, contextual projections, Visual Graph Resolver | FS-02, FS-05, FS-07, FS-14, FS-16 |
| TE-03 — Tenant Provisioning + RLS | SESSION_CONTEXT setup, EF Core interceptor, SQL Server RLS predicates, Polly error handling | FS-03, FS-14 |
| TE-04 — Transactional Outbox | Reliable async event dispatch, at-least-once delivery, DLQ handling | FS-03, FS-06, FS-11, FS-15 |
| TE-05 — Distributed Saga (Dapr) | Multi-step workflow orchestration with compensation, Dapr state store | FS-10, FS-12 |
| TE-06 — CQRS Projection Rebuild | Read-side projection reconstruction on schema change, Dapper queries | FS-04, FS-07, FS-13 |
UMS maintains full bidirectional traceability from business requirement to code:
flowchart TD
classDef fs fill:#4a1a6b,stroke:#9c27b0,color:#fff,font-weight:bold
classDef ts fill:#1e3a5f,stroke:#3b82f6,color:#fff
classDef te fill:#4a3800,stroke:#f59e0b,color:#fff
classDef adr fill:#14532d,stroke:#22c55e,color:#fff
FS_BOX[" 16 Functional Stories (FS)\nBusiness requirements with acceptance criteria\nFS-01 → FS-16"]:::fs
TS_BOX["️ 89 Technical Stories (TS)\nMVP: 253 pts · Post-MVP: 325 pts\n5–9 TS per FS"]:::ts
TE_BOX[" 6 Technical Enablers (TE)\nCross-cutting infrastructure investments\nTE-01 → TE-06"]:::te
ADR_BOX[" 57+ Evolith ADRs\nEvery TE implements one or more ADRs\nFull bidirectional traceability"]:::adr
FS_BOX --> TS_BOX --> TE_BOX --> ADR_BOX
Every line of UMS code can be traced back to a functional requirement, a technical decision, and an Evolith ADR. This is the traceability model Evolith mandates (ADR-0040, V-07).
These are the Evolith ADRs most heavily tested by UMS — organized by the architectural concern they address:
| ADR | Decision | UMS Evidence |
|---|---|---|
| ADR-0001 | Nx Monorepo Orchestration | Monorepo with strict lib boundaries and domain isolation |
| ADR-0002 | Hexagonal Architecture | Ports + Adapters across all 8 bounded contexts |
| ADR-0047 | Modular Monolith Selection | UMS is a Phase 1 modular monolith — extraction-ready but not extracted |
| ADR | Decision | UMS Evidence |
|---|---|---|
| ADR-0010 | Dual-Layer RLS Strategy | root_tenant_id on every table, EF Core filter + SQL Server RLS predicate |
| ADR-0031 | Schema-per-Context | 8 separate schemas, one per bounded context |
| ADR-0051 | SQL Server 2022 | Closure table, partitioning, temporal tables, RLS |
| ADR-0057 | EF Core 8 + Dapper | EF Core for writes, Dapper for complex read projections |
| ADR | Decision | UMS Evidence |
|---|---|---|
| ADR-0012 | RBAC/ABAC Guards | Permission template system with contextual overrides |
| ADR-0021 | Auth Graph Compilation | DAG compiler in TE-02 |
| ADR | Decision | UMS Evidence |
|---|---|---|
| ADR-0015 | Injectable Event Bus | In-process bus upgradeable to RabbitMQ without domain changes |
| ADR-0033 | Transactional Outbox | TE-04, used by Compliance and Approvals contexts |
| ADR-0035 | Distributed Sagas | TE-05 via Dapr, used by Approvals (EP-06) and IGA (EP-08) |
| ADR-0034 | CQRS Applicability | Read/write split at protocol level (Dapper queries / EF Core commands) |
| ADR | Decision | UMS Evidence |
|---|---|---|
| ADR-0007 | OTel + Loki | Every use case has an OTel span; W3C TraceContext propagated end-to-end |
| ADR-0016 | Immutable Audit Trail | Append-only audit table with 10-column standard schema (EP-04) |
| ADR-0018 | Testing Pyramid | 70% unit / 20% integration / 10% E2E enforced in GitHub Actions CI |
Use these links to go directly to the relevant section of the UMS documentation without leaving the architectural context:
| Resource | What you'll find |
|---|---|
| UMS Architecture Portal | Bounded context map, architectural decisions, C4 diagrams, ADR registry |
| UMS Master Index | Complete navigation map of all UMS documentation |
| UMS ADR Registry | Product-level ADRs that extend and specialize Evolith decisions |
| Resource | What you'll find |
|---|---|
| UMS Repository Root | Source code, setup instructions, project structure |
| UMS README | Stack overview, local setup, how to run the application |
| Construction Plan — Technical Stories | 89 technical stories with effort estimates, team profiles, sprint guidance |
| FS-to-TS Mapping | Traceability from every functional story to its technical stories |
| Resource | What you'll find |
|---|---|
| UMS Test Architecture | Testing pyramid, contract test strategy, coverage gates |
| UMS CI Pipeline | GitHub Actions workflow — unit, integration, security, coverage |
| Resource | What you'll find |
|---|---|
| UMS Infrastructure Setup | Docker Compose stack, OTel collector config, Grafana setup |
| UMS Observability Stack | OTel + Loki + Tempo + Grafana configuration and runbooks |
| Resource | What you'll find |
|---|---|
| UMS Documentation Index | All functional stories, acceptance criteria, epic structure |
| Construction Phase Overview | MVP timeline, team composition, sprint planning guidance |
Understanding the boundary is as important as understanding what UMS does:
| UMS is | UMS is not |
|---|---|
| The executable proof of Evolith decisions | The source of those decisions (Evolith owns the ADRs) |
| A reference for architectural patterns in .NET 8 | A starter template to copy into production |
| An evolving product that can promote discoveries back to Evolith | A frozen demo that stays unchanged |
| Owned and operated independently from Evolith | Part of this repository |
See Reference vs Applied Model for the full boundary definition.