# db-error-presentation Specification ## Purpose TBD - created by archiving change db-error-translation. Update Purpose after archive. ## Requirements ### Requirement: Constraint violations render as field-level validation errors When a database write fails with a constraint violation that `web.FieldErrorsFromDB` can translate (a constraint named in the handler's `ConstraintMessages` map, or a violation class with a generic fallback), the handler SHALL render the failure as a validation outcome: HTTP 422 with the page's `FieldErrors` re-render for form-backed mutations (per operator-ux-conventions §6/§8), or the translated friendly message through the surface's existing banner/toast shape where no `FieldErrors` machinery exists (member partials, formless actions such as revoke/delete buttons). Handlers SHALL name the constraints their form can plausibly hit in a `ConstraintMessages` map co-located with the handler, each mapped to the form field to flag and an actionable message. #### Scenario: Duplicate workspace slug renders a friendly field message - **WHEN** a member submits the workspace create form with a slug that already exists in their organization and the insert fails on the `workspaces` `UNIQUE (org_id, slug)` constraint - **THEN** the form SHALL re-render with the message "A workspace with this slug already exists in this organization." - **AND** the response SHALL NOT contain the constraint name, an SQLSTATE code, or any other driver text #### Scenario: Ladder-overlap exclusion renders on the grant form - **WHEN** an operator's grant issuance loses a race and the transition fails on the `excl_pool_provision_ladders_active_overlap` exclusion constraint - **THEN** the handler SHALL return 422 and re-render the composite with a field-level message on the issuing form explaining the pool already has an active plan on that ladder #### Scenario: Unnamed constraint falls back to a class-level message - **WHEN** a write fails on a constraint the handler did not name but whose SQLSTATE class the translator recognizes (unique, foreign-key, check, or value-too-long) - **THEN** the handler SHALL render the translator's generic class message through the same validation path, not the raw error ### Requirement: Raw database driver text never reaches the UI Handlers SHALL NOT render the raw text of a database error (`err.Error()`) — including SQLSTATE codes, constraint or relation names, and driver prefixes — in any response body, banner, toast, or `http.Error` output. When `web.FieldErrorsFromDB` cannot translate a write failure (`ok == false`), the handler SHALL log the full error via `slog` and render a generic failure message that directs the operator to the server logs. #### Scenario: Untranslatable write failure is logged and rendered generically - **WHEN** a mutation fails with an error that is not a recognizable constraint violation (for example a lost connection or unexpected schema drift) - **THEN** the handler SHALL record the underlying error with `slog.Error` - **AND** the rendered response SHALL contain only a generic failure message (e.g. "Failed to update the product. Details are in the server logs.") with no fragment of the driver error text #### Scenario: Error toasts carry no driver text - **WHEN** a formless operator action (such as revoking a grant) fails on a translatable constraint - **THEN** the error toast and banner SHALL carry the translated friendly message and SHALL NOT include SQLSTATE codes or constraint names ### Requirement: Lint enforces the no-raw-error contract The `member-console lint` run SHALL include a rule (`raw-error-render`) that flags any line in a non-test Go file under `internal/server` where an error's raw text (`err.Error()` on any identifier ending in `err`/`Err`) is concatenated into or passed as an argument to a UI sink — a `render*` helper, `fireErrorToast`, `fireSuccessToast`, or `http.Error`. Lines that reference `slog.` SHALL be exempt, since logging the raw error is the required behaviour. #### Scenario: Leaky render line is flagged - **WHEN** lint scans a handler containing `h.renderFooPage(w, r, "", "Failed to save: "+err.Error())` - **THEN** the run SHALL report a `raw-error-render` violation with that file and line #### Scenario: Structured logging is exempt - **WHEN** lint scans a line such as `h.Logger.Error("failed to save", slog.Any("error", err))` - **THEN** the run SHALL NOT report a violation for that line