purchasability-sync-completion changes Deferred live verification for ladder-tier-append and multi-price-support to maintainer; stack left running. Validated ladder-tier-append with openspec --strict. Closed purchasability checklist issue and marked milestone 10c Done. Archived specs into archive directory and created db-error-presentation spec. Updated plan-ladder-management and price-management specs with tier append, reorder, removal, and price default behavior.
4.4 KiB
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
workspacesUNIQUE (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_overlapexclusion 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-renderviolation 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