# Client-Safe Error Boundary Design **Date:** 2026-08-02 **Status:** approved by the user's instruction to apply the detailed P1/P2 review sequentially **Scope:** HTTP error envelopes in `adapter:inbound:web` and the `sample-portfolio` domain advice ## Context Several handlers pass `Exception#getMessage()`, rejected request values, or a raw request URL into the public error envelope. Those values are not a stable API contract and can contain identifiers, tokens, uploaded values, configuration details, or internal diagnostics. Persistence and outbound dependency failures already use fixed client-safe messages; the rest of the HTTP boundary must follow the same rule. ## Decision The inbound adapter owns a message allowlist keyed by stable error code. Handlers may expose only: - stable `code`, `category`, HTTP status, and `retryable` from `ApiErrorCode`; - fixed, code-specific client messages; - bounded structural details such as field name, validation reason code, expected Java type, supported HTTP methods, or supported media types. They must not expose exception messages, rejected values, raw request URLs, adapter/configuration diagnostics, opaque cursors, authentication diagnostics, resource identifiers, or duplicate domain values. Bean Validation interpolated/default messages are also discarded because custom templates can include the validated value. Validation details contain only normalized server-owned property names plus allowlisted reason codes and fixed messages; collection/map keys and indices are removed. `ClientSafeErrorMessages` is extended for skeleton-wide operational codes. The sample keeps its domain wording in a separate package-private `PortfolioClientSafeErrorMessages`, preserving the rule that production modules do not know sample business concepts. ## Public Messages Representative mappings are fixed as follows: - `MAPPING_FAILED` → `Request data could not be mapped`; - `BAD_PARAMETER` → `Request parameter is invalid`; - `INVALID_TOKEN` → `Authentication token is invalid`; - `UNAUTHENTICATED` → `Authentication is required`; - authorization denials → `Access is denied`; - `PRECONDITION_FAILED` → `Resource state changed; refresh and retry`; - page/cursor failures → generic corrective text, with safe field/reason details retained; - `ADAPTER_DISABLED` and internal classifications → `Internal server error`; - domain not-found/conflict/invariant codes → fixed noun-level text with no ID/title value. Transport overrides use fixed wording and retain only safe protocol metadata. For example, 405 still emits `Allow`, while both controller-route (`NoHandlerFoundException`) and static-resource (`NoResourceFoundException`) 404s use the same envelope without echoing the request URL. ## Testing Tests inject conspicuous secret sentinels into exception messages, rejected values, URLs, tokens, IDs, and duplicate titles. Every resulting response must preserve its status/code/category while excluding the sentinel from both `error.message` and `error.details`. Validation tests additionally place sentinels in interpolated/default messages and iterable keys/indices. A real MockMvc resource-resolution request verifies the Spring 7 `NoResourceFoundException` path rather than calling the advice method directly. The focused module suites remain the primary verification: - `:adapter:inbound:web:test` for operational and transport handlers; - `:sample-portfolio:test` for domain advice and sample wire behavior; - `verifyCleanArchitectureDependencies` for dependency direction. ## Non-Goals - no change to error codes, categories, statuses, or retryability; - no suppression of server-side logs or tracing in this batch; - no application/domain dependency on HTTP response types; - no generic exception-message sanitizer based on regexes or truncation; - no staging, commit, amend, or push by an agent.