Files
cgalo5758 7c20ec8988 Archive ladder-tier-append, multi-price-support, and
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.
2026-07-03 14:20:53 -05:00

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 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