Promoted to Evolith: This ADR has been elevated to Evolith ADR-0047 — Actionable User Error Contract and Correlated Diagnostics. UMS retains this document as implementation reference.
Status: Accepted
Date: 2026-05-26
Decision Owner: Architecture
Evolith disposition: Proposed for adoption as a universal Evolith standard
Related:
A quality review of the System Suite module registration workflow exposed two conflicting failure modes:
For example, a user submitting a module description longer than the allowed limit should be told how to fix the data. That user must not see a namespace, class name, database detail, exception message, or stack trace. Support engineers still need a reference that locates the complete technical event in Serilog and Grafana Loki.
The decision applies across all user-initiated commands in UMS, not only module registration.
UMS adopts a two-channel error contract:
| Error class | Example | User-visible content | Technical logging |
|---|---|---|---|
| Validation failure | Maximum length exceeded | Actionable correction and tracking code | Optional structured event |
| Business conflict | Duplicate module code | Business-safe reason and tracking code | Structured event when operationally useful |
| Authorization or authentication | Access rejected | Safe access statement and tracking code | Security-aware structured event |
| Infrastructure or unexpected failure | Database, resolver, unhandled exception | Generic retry guidance and tracking code | Full sanitized exception with correlation |
REST command failures use RFC 7807 Problem Details. The public payload may include:
{
"type": "https://httpstatuses.io/422",
"title": "Validation Error",
"status": 422,
"detail": "No se pudo completar la operacion porque existen campos invalidos.",
"userMessage": "No se pudo registrar el modulo porque el campo Codigo tiene un formato invalido. Use solo letras, numeros y guion bajo, por ejemplo REPORTS_01. Valor ingresado: DDDD-!.",
"errorCode": "validation.invalid_format",
"messageKey": "system_suite.module.code_invalid_format",
"messageParameters": { "invalidValue": "DDDD-!" },
"errorId": "0cd26dd6-d50e-4b3c-a662-8098a87569a4",
"traceId": "<distributed-trace-id>"
}
Rules:
errorId is a server-generated GUID created once for each failed request. It is required in the response, response header (X-Error-Id), and corresponding Serilog event.traceId and X-Correlation-Id remain distributed-tracing identifiers and must not replace errorId.userMessage, when present, is explicitly safe for presentation.errorCode, messageKey, and messageParameters are the target contract for localized clients.detail must never contain stack traces, exception type names, namespaces, source paths, SQL statements, tokens, secrets, or PII.detail, raw GraphQL messages, or native exception messages. They display only approved fields or a local fallback.The current UMS implementation produces userMessage through API localization resources selected by X-Language or Accept-Language. Stable messageKey support remains the evolution path for richer clients.
GraphQL is used for reads according to ADR-0055. GraphQL errors exposed to clients use safe localized generic messages and carry errorId. Resolver exceptions are logged through Serilog with that same errorId, but are not serialized to the browser.
errorId GUID.ErrorId for expected failures and records full sanitized exceptions with ErrorId for unexpected failures; Loki queries use that property.CorrelationId and TraceId continue to link distributed operations independently from the support-facing ErrorId.User feedback is delivered through the centralized notification mechanism:
| Category | Safe user-facing example |
|---|---|
| Validation | No se pudo registrar el modulo porque el campo Codigo tiene un formato invalido. Use solo letras, numeros y guion bajo, por ejemplo REPORTS_01. Valor ingresado: DDDD-!. |
| Business rule | No se pudo registrar el modulo porque ya existe otro modulo con el mismo Codigo. Ingrese un codigo diferente. |
| Authorization | No se pudo completar la operacion porque no tiene permisos suficientes. Solicite acceso al administrador. |
| Unexpected | No se pudo completar la operacion debido a un error inesperado. Intente nuevamente mas tarde. |
Each displayed failure appends: Si necesitas mas detalles, consulta con el administrador e indica este ID de error: {errorId}.
detail valuesRejected. It is convenient but allows implementation details or PII to escape when an endpoint is misconfigured.
Rejected. It prevents users from correcting expected business or validation conditions, increasing retries and support requests.
Rejected. QA and shared environments are still user-facing surfaces and may contain production-like data.
userMessage text to fully localized client message keys.| Concern | UMS implementation |
|---|---|
| REST safe mapping | Ums.Presentation/Extensions/ResultExtensions.cs |
| Error ID generation | Ums.Presentation/Extensions/UserFacingErrorContext.cs |
| Unhandled exceptions | Ums.Presentation/Middleware/GlobalExceptionHandler.cs |
| GraphQL sanitization | Ums.Presentation/GraphQL/SafeGraphQlErrorFilter.cs |
| Localization resources | Ums.Globalization/Resources/{language}/domain-errors.json |
| Safe web extraction | src/application/errors/http-error.ts |
| Notification composition | src/application/hooks/use-notified-mutation.ts |
| Manual dismissal | src/application/stores/notification.store.ts and src/presentation/shared/components/ToastQueue.tsx |
errorId, the matching X-Error-Id header, and traceId.| ADR Registry | Notification and Feedback Blueprint |