fix(api): define exact v2 endpoint error contracts - #448
Merged
Conversation
Makisuo
force-pushed
the
codex/http-error-consistency
branch
4 times, most recently
from
August 13, 2026 12:10
8692472 to
33356a7
Compare
Makisuo
force-pushed
the
codex/http-error-consistency
branch
from
August 13, 2026 13:47
33356a7 to
434d855
Compare
Makisuo
marked this pull request as ready for review
August 13, 2026 13:48
🍁 Maple PR previewNote Preview resources were removed when this pull request closed. Final commit |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
HttpTaggedError: the class owns its tag, HTTP status, public code/title/copy policy, retry behavior, recovery action, parameter, and retry timingerrorbody; handlers now fail with the original error and require notoV2Erroror generic boundary serializerpublicError(ErrorClass), so every operation declares the literal tag union it can return in OpenAPIdefineV2Errorfor failures born at the v2 boundary, keeping each constructor and literal-tag schema inseparableError model
Each expected failure has one definition:
HttpTaggedErrorowns its semantic_tag, status, safe presentation, retry behavior, and recovery guidance.publicError(ErrorClass)projects the exact wire schema used by an endpoint and OpenAPI.There is no
toV2Error, status-family remapper, route-local serialization table, or generic expected-error boundary.catchTagremains 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_tagwas juststring, so preserving a runtime tag did not make it available to generated clients.Now every error branch has a one-value
_tagenum. 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:
_tagis the canonical exact branching key and is now a literal union per operationInvalidRequestError,ResponseSchemaError, andUnexpectedErrorInsufficientScopeErrorfromInsufficientPermissionsErrorRaw OAuth callbacks, webhooks, streaming routes, and internal protocols keep their protocol-specific wire formats while using typed internal failures.
Validation
bun typecheck: 40/40 tasks passed across 37 packagespackages/domain: 36 files, 478 tests passedapps/api: 150 files passed, 5 skipped; 1,736 tests passed, 202 skippedgit diff --checkpassedSingle client error model
PublicHttpErrorBodySchemais 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.displayErrorreturns 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.UnexpectedError; that is the only generic fallback.{ _tag, message }shape.error-messages.tsis 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.