molecule-core/workspace-server/internal/middleware
Hongming Wang 5e36c6638c feat(platform,canvas): classify "datastore unavailable" as 503 + dedicated UI
User reported the canvas threw a generic "API GET /workspaces: 500
{auth check failed}" error when local Postgres + Redis were both
down. Two problems:

1. The error code (500) and message ("auth check failed") said
   nothing useful. The actual condition was "platform can't reach
   its datastore to validate your token" — a Service Unavailable
   class, not Internal Server Error.

2. The canvas had no way to distinguish infra-down from a real
   auth bug, so it rendered the raw API string in the same
   generic-error overlay it uses for everything.

Fix in two layers:

Server (wsauth_middleware.go):
  - New abortAuthLookupError helper centralises all three sites
    that previously returned `500 {"error":"auth check failed"}`
    when HasAnyLiveTokenGlobal or orgtoken.Validate hit a DB error.
  - Now returns 503 + structured body
    `{"error": "...", "code": "platform_unavailable"}`. 503 is
    the correct semantic ("retry shortly, infra is unavailable")
    and the code field is the contract the canvas reads.
  - Body deliberately excludes the underlying DB error string —
    production hostnames / connection-string fragments must not
    leak into a user-visible error toast.

Canvas (api.ts):
  - New PlatformUnavailableError class. api.ts inspects 503
    responses for the platform_unavailable code and throws the
    typed error instead of the generic "API GET /…: 503 …"
    message. Generic 503s (upstream-busy, etc.) keep the legacy
    path so existing busy-retry UX isn't disrupted.

Canvas (page.tsx):
  - New PlatformDownDiagnostic component renders when the
    initial hydration catches PlatformUnavailableError.
    Surfaces the actual condition with operator-actionable
    copy ("brew services start postgresql@14 / redis") +
    pointer to the platform log + a Reload button.

Tests:
  - Go: TestAdminAuth_DatastoreError_Returns503PlatformUnavailable
    pins the response shape (status, code field, no DB-error leak)
  - Canvas: 5 tests for PlatformUnavailableError classification —
    typed throw on 503+code match, generic-Error fallback for
    503-without-code (upstream busy), 500 stays generic, non-JSON
    body falls back to generic.

1015 canvas tests + full Go middleware suite pass.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-26 00:01:56 -07:00
..
devmode_test.go refactor(middleware): extract dev-mode fail-open predicate 2026-04-23 14:55:34 -07:00
devmode.go fix: six UX bugs (peers auth, scroll, chat tabs, config persist, + visibility) 2026-04-23 20:18:30 -07:00
mcp_ratelimit_test.go chore: open-source restructure — rename dirs, remove internal files, scrub secrets 2026-04-18 00:24:44 -07:00
mcp_ratelimit.go chore: open-source restructure — rename dirs, remove internal files, scrub secrets 2026-04-18 00:24:44 -07:00
ratelimit_test.go chore: open-source restructure — rename dirs, remove internal files, scrub secrets 2026-04-18 00:24:44 -07:00
ratelimit.go fix: dev-mode bypass for IP rate limiter + 429 retry on GET 2026-04-23 20:44:09 -07:00
securityheaders_test.go chore: open-source restructure — rename dirs, remove internal files, scrub secrets 2026-04-18 00:24:44 -07:00
securityheaders.go chore: open-source restructure — rename dirs, remove internal files, scrub secrets 2026-04-18 00:24:44 -07:00
session_auth_test.go fix(canvas/a11y): restore aria-hidden on backdrop div after cherry-pick conflict 2026-04-24 03:10:18 +00:00
session_auth.go fix(canvas/a11y): aria-hidden SVGs, MissingKeysModal dialog, session cookie auth 2026-04-24 04:30:26 +00:00
tenant_guard_test.go fix(tenant-guard): allowlist /registry/register + /registry/heartbeat (#1236) 2026-04-21 02:47:27 +00:00
tenant_guard.go fix(tenant-guard): allowlist /registry/register + /registry/heartbeat (#1236) 2026-04-21 02:47:27 +00:00
wsauth_middleware_org_id_test.go test(middleware): add last_used_at ExpectExec for WorkspaceAuth org-token tests 2026-04-24 13:01:42 +00:00
wsauth_middleware_test.go feat(platform,canvas): classify "datastore unavailable" as 503 + dedicated UI 2026-04-26 00:01:56 -07:00
wsauth_middleware.go feat(platform,canvas): classify "datastore unavailable" as 503 + dedicated UI 2026-04-26 00:01:56 -07:00