Type: Architecture Blueprint
Status: Accepted, amended 2026-05-26
Runtime: React 18, TypeScript, TanStack Query v5, Zustand, Axios
Code location: src/apps/ums.web-app/src
Governing decision: ADR-0066: Actionable User Error Contract and Correlated Diagnostics
Every user-initiated command must produce visible feedback that is both actionable and safe. UMS uses a dual-visibility system:
Feedback is not a technical diagnostic surface. The browser receives only content approved for users and a server-generated errorId GUID that support can search in Serilog and Grafana Loki.
| Failure mode | User impact | Standard response |
|---|---|---|
| Silent command failure | User repeats an operation without knowing it failed | Always emit a success or error notification |
| Generic validation failure | User cannot correct input | Present an approved, actionable userMessage |
| Technical detail leakage | User sees stack, class, namespace, SQL, or internals | Display only approved fields; keep diagnostics in logs |
| Lost support correlation | Support cannot locate a failure report | Include server-generated errorId in response, header, and log |
| Ineffective close control | Dismissed message remains in active history | Manual close removes the notification entry |
getHttpErrorMessage(error, fallback) is the only client function that chooses user-facing error content. Components and feature hooks must not display raw response fields.
Allowed display sources:
userMessage, explicitly approved by the API boundary.messageKey and safe parameters when implemented.Forbidden display sources:
detail or message.Validation and business-rule failures may be displayed when the API has classified them as safe. Examples include:
An unexpected infrastructure or programming failure always receives generic guidance plus the tracking code.
All feature mutations use useNotifiedMutation. It performs the operation, invalidates successful cache entries, selects approved feedback, and adds a notification to the single Zustand store.
| Responsibility | Component |
|---|---|
| Safe error extraction | src/application/errors/http-error.ts |
| Command notification wrapper | src/application/hooks/use-notified-mutation.ts |
| Notification state | src/application/stores/notification.store.ts |
| Ephemeral display | src/presentation/shared/components/ToastQueue.tsx |
| History display | src/presentation/shared/components/NotificationCenter.tsx |
When a failed operation carries an errorId, the notification includes support guidance such as:
If you need more details, contact an administrator and provide this error ID: <errorId>.
The code is safe to show because it is a support reference, not an internal exception description. It must correlate with the server log context.
| User interaction | Toast | Notification center |
|---|---|---|
| Automatic timeout | Removed from ephemeral display | May remain in history |
| Manual close button | Removed immediately | Corresponding active entry is removed |
| Clear all in center | Removed when present | All entries removed |
Manual dismissal is an explicit user intent to remove that feedback item, not merely to hide its animation.
REST commands return RFC 7807 Problem Details. A safe validation response can take this form:
{
"type": "https://httpstatuses.io/422",
"title": "Validation Error",
"status": 422,
"detail": "The operation could not be completed because one or more fields are invalid.",
"userMessage": "The module could not be registered because the Code field has an invalid format. Use letters, numbers, and underscores only, for example REPORTS_01. Entered value: DDDD-!.",
"errorCode": "validation.invalid_format",
"messageKey": "system_suite.module.code_invalid_format",
"messageParameters": { "invalidValue": "DDDD-!" },
"errorId": "<server-generated-guid>",
"traceId": "<distributed-trace-id>"
}
Contract requirements:
detail is safe but is not automatically displayable.userMessage is displayable only because the API boundary explicitly publishes it as safe.errorCode, messageKey, and parameters are the preferred multilingual evolution.errorId is generated once per failed request and appears in the payload, X-Error-Id, and Serilog event used by support.traceId remains available for distributed diagnostics and is distinct from errorId.GraphQL queries follow the same safety rule: unhandled resolver errors return safe client content and retain diagnostics in logs.
UMS currently supports:
userMessage for approved REST validation and business conflict feedback.errorId in REST error payloads and X-Error-Id, prioritized by the web client for support.errorId recorded in the corresponding server log.The target enhancement is to introduce stable errorCode or messageKey values and client-side localization rather than serving final language text from the API.
| Layer | Required verification |
|---|---|
| API | Expected validation exposes safe actionable feedback, GUID errorId, matching X-Error-Id, and traceId |
| API | Unexpected errors expose no stack trace, exception type, namespace, SQL, secret, or PII |
| Web extraction | Only approved messages or local fallback are displayed |
| Mutation wrapper | Localized support guidance with errorId is included when available |
| Toast queue | Manual close removes the notification from active state |
| Back to Blueprints Index | Version en Espanol |