Skip to content

fix(api): define exact v2 endpoint error contracts - #448

Merged
Makisuo merged 4 commits into
mainfrom
codex/http-error-consistency
Aug 13, 2026
Merged

fix(api): define exact v2 endpoint error contracts#448
Makisuo merged 4 commits into
mainfrom
codex/http-error-consistency

Conversation

@Makisuo

@Makisuo Makisuo commented Aug 12, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • make expected domain failures self-describing with HttpTaggedError: the class owns its tag, HTTP status, public code/title/copy policy, retry behavior, recovery action, parameter, and retry timing
  • let each tagged error expose its own safe v2 error body; handlers now fail with the original error and require no toV2Error or generic boundary serializer
  • derive exact endpoint schemas with publicError(ErrorClass), so every operation declares the literal tag union it can return in OpenAPI
  • add defineV2Error for failures born at the v2 boundary, keeping each constructor and literal-tag schema inseparable
  • expose the complete warehouse/query-engine failure unions where those services are actually used, including quota, malformed-query, schema-drift, timeout, and result-mismatch tags
  • give request decoding, credentials, scopes, rate limits, response-schema failures, defects, resource-specific not-found cases, telemetry validation, and session-replay limits stable explicit tags
  • delete the alert, warehouse, investigation, and per-domain remapping tables plus route-local generic error translation
  • add a consistent v1 validation/defect boundary and document what should move to v2 versus remain protocol-specific or internal

Error model

Each expected failure has one definition:

  • HttpTaggedError owns its semantic _tag, status, safe presentation, retry behavior, and recovery guidance.
  • The error instance materializes that safe public body itself.
  • publicError(ErrorClass) projects the exact wire schema used by an endpoint and OpenAPI.
  • A v2 handler returns the service effect unchanged; the HTTP schema reads the original error directly.

There is no toV2Error, status-family remapper, route-local serialization table, or generic expected-error boundary. catchTag remains only where one specific internal failure deliberately becomes a different public failure, such as the session-replay response-size limit. Unexpected defects still go through the sanitized transport boundary.

What changed in the contract

Effect merges endpoint and middleware errors by HTTP status and emits same-status alternatives as OpenAPI anyOf. Previously those alternatives referenced broad schemas whose _tag was just string, so preserving a runtime tag did not make it available to generated clients.

Now every error branch has a one-value _tag enum. Each operation's OpenAPI response describes its actual discriminated union, including middleware errors. A contract-wide test traverses every 4xx/5xx response and fails if a branch has a broad tag, no tag, or a duplicate tag.

The endpoint contract is also checked against the handler's Effect error channel. This exposed previously incomplete declarations: alert test/preview can return warehouse quota and Maple query failures, and PlanetScale organization calls can return revoked authorization. Those outcomes are explicit instead of hiding behind generic 429/500/503 families.

Breaking error-contract changes

This intentionally does not preserve existing v2 error-code consumers:

  • _tag is the canonical exact branching key and is now a literal union per operation
  • dynamic endpoint-derived tags are replaced by stable definitions such as InvalidRequestError, ResponseSchemaError, and UnexpectedError
  • permission failures distinguish InsufficientScopeError from InsufficientPermissionsError
  • resource 404s, telemetry validation failures, session-replay limits, warehouse failures, and query-engine failures retain their semantic tags
  • integration not-connected remains 409, revoked authorization is 401, stored corruption and Maple-generated malformed SQL are 500, and query-engine timeouts remain 504
  • generated client error unions are more precise; in-repo consumers read the v2 envelope directly

Raw OAuth callbacks, webhooks, streaming routes, and internal protocols keep their protocol-specific wire formats while using typed internal failures.

Validation

  • full workspace bun typecheck: 40/40 tasks passed across 37 packages
  • packages/domain: 36 files, 478 tests passed
  • apps/api: 150 files passed, 5 skipped; 1,736 tests passed, 202 skipped
  • v2 route suite: 11 files, 131 tests passed
  • v1 route regression suite: 9 files, 62 tests passed
  • Effect lint passed
  • git diff --check passed

Single client error model

  • PublicHttpErrorBodySchema is now the runtime contract shared by domain errors, v2 responses, batched per-item failures, and the web client. Its tag, type, code, title, message, retryability, and recovery action are required.
  • displayError returns declared public bodies unchanged. The web client no longer creates normalized/formatted/category objects, translates status codes into copy, renames metadata fields, humanizes backend messages, or infers recovery.
  • Browser-native failures (network, timeout, invalid URL, stale chunk) are authored directly in the same public shape. Unknown values collapse to one sanitized UnexpectedError; that is the only generic fallback.
  • Local warehouse client errors own the same public body, and query batch failures now return the complete body instead of the lossy { _tag, message } shape.
  • error-messages.ts is reduced from 401 lines to 135, and this commit removes 333 more lines than it adds.

Additional validation: full web suite, 177 files and 1,536 tests passed; React Doctor reports no findings in the changed files.

@Makisuo
Makisuo force-pushed the codex/http-error-consistency branch 4 times, most recently from 8692472 to 33356a7 Compare August 13, 2026 12:10
@Makisuo Makisuo changed the title fix(api): unify HTTP error handling fix(api): define exact v2 endpoint error contracts Aug 13, 2026
Base automatically changed from feat/dashboard-sections-tabs to main August 13, 2026 13:43
@Makisuo
Makisuo force-pushed the codex/http-error-consistency branch from 33356a7 to 434d855 Compare August 13, 2026 13:47
@Makisuo
Makisuo marked this pull request as ready for review August 13, 2026 13:48
@Makisuo
Makisuo merged commit 98e24c4 into main Aug 13, 2026
31 checks passed
@Makisuo
Makisuo deleted the codex/http-error-consistency branch August 13, 2026 15:27
@Makisuo
Makisuo deployed to pr-preview August 13, 2026 15:27 — with GitHub Actions Active
@github-actions

Copy link
Copy Markdown

🍁 Maple PR preview

Note

Preview resources were removed when this pull request closed.

Final commit 31b18d8 · View workflow run

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant